Replaced logger class with simpler functions.

This commit is contained in:
Richard Moore 2022-09-08 23:21:08 -04:00
parent 2740976d8b
commit 29949a6309
74 changed files with 886 additions and 823 deletions

@ -1,6 +1,6 @@
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
import { logger } from "../utils/logger.js"; import { assertArgumentCount, throwArgumentError } from "../utils/index.js";
import { Coder, Reader, Result, Writer } from "./coders/abstract-coder.js"; import { Coder, Reader, Result, Writer } from "./coders/abstract-coder.js";
import { AddressCoder } from "./coders/address.js"; import { AddressCoder } from "./coders/address.js";
@ -49,7 +49,7 @@ export class AbiCoder {
if (match) { if (match) {
let size = parseInt(match[2] || "256"); let size = parseInt(match[2] || "256");
if (size === 0 || size > 256 || (size % 8) !== 0) { if (size === 0 || size > 256 || (size % 8) !== 0) {
logger.throwArgumentError("invalid " + match[1] + " bit length", "param", param); throwArgumentError("invalid " + match[1] + " bit length", "param", param);
} }
return new NumberCoder(size / 8, (match[1] === "int"), param.name); return new NumberCoder(size / 8, (match[1] === "int"), param.name);
} }
@ -59,12 +59,12 @@ export class AbiCoder {
if (match) { if (match) {
let size = parseInt(match[1]); let size = parseInt(match[1]);
if (size === 0 || size > 32) { if (size === 0 || size > 32) {
logger.throwArgumentError("invalid bytes length", "param", param); throwArgumentError("invalid bytes length", "param", param);
} }
return new FixedBytesCoder(size, param.name); return new FixedBytesCoder(size, param.name);
} }
return logger.throwArgumentError("invalid type", "type", param.type); return throwArgumentError("invalid type", "type", param.type);
} }
getDefaultValue(types: ReadonlyArray<string | ParamType>): Result { getDefaultValue(types: ReadonlyArray<string | ParamType>): Result {
@ -74,7 +74,7 @@ export class AbiCoder {
} }
encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string { encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string {
logger.assertArgumentCount(values.length, types.length, "types/values length mismatch"); assertArgumentCount(values.length, types.length, "types/values length mismatch");
const coders = types.map((type) => this.#getCoder(ParamType.from(type))); const coders = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = (new TupleCoder(coders, "_")); const coder = (new TupleCoder(coders, "_"));

@ -1,9 +1,7 @@
import { zeroPadBytes } from "../utils/data.js"; import {
getBytes, toUtf8Bytes, toUtf8String, zeroPadBytes
import { logger } from "../utils/logger.js"; } from "../utils/index.js";
import { toUtf8Bytes, toUtf8String } from "../utils/utf8.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -21,7 +19,7 @@ export function formatBytes32String(text: string): string {
} }
export function parseBytes32String(_bytes: BytesLike): string { export function parseBytes32String(_bytes: BytesLike): string {
const data = logger.getBytes(_bytes, "bytes"); const data = getBytes(_bytes, "bytes");
// Must be 32 bytes with a null-termination // Must be 32 bytes with a null-termination
if (data.length !== 32) { throw new Error("invalid bytes32 - not 32 bytes long"); } if (data.length !== 32) { throw new Error("invalid bytes32 - not 32 bytes long"); }

@ -1,9 +1,9 @@
import { toArray, toBigInt, toNumber } from "../../utils/maths.js"; import {
import { concat, hexlify } from "../../utils/data.js"; defineProperties, concat, getBytesCopy, getNumber, hexlify,
import { defineProperties } from "../../utils/properties.js"; toArray, toBigInt, toNumber,
assertPrivate, throwArgumentError, throwError
import { logger } from "../../utils/logger.js"; } from "../../utils/index.js";
import type { BigNumberish, BytesLike } from "../../utils/index.js"; import type { BigNumberish, BytesLike } from "../../utils/index.js";
@ -22,7 +22,7 @@ export class Result extends Array<any> {
[ K: string | number ]: any [ K: string | number ]: any
constructor(guard: any, items: Array<any>, keys?: Array<null | string>) { constructor(guard: any, items: Array<any>, keys?: Array<null | string>) {
logger.assertPrivate(guard, _guard, "Result"); assertPrivate(guard, _guard, "Result");
super(...items); super(...items);
// Name lookup table // Name lookup table
@ -44,7 +44,7 @@ export class Result extends Array<any> {
get: (target, prop, receiver) => { get: (target, prop, receiver) => {
if (typeof(prop) === "string") { if (typeof(prop) === "string") {
if (prop.match(/^[0-9]+$/)) { if (prop.match(/^[0-9]+$/)) {
const index = logger.getNumber(prop, "%index"); const index = getNumber(prop, "%index");
if (index < 0 || index >= this.length) { if (index < 0 || index >= this.length) {
throw new RangeError("out of result range"); throw new RangeError("out of result range");
} }
@ -153,7 +153,7 @@ function getValue(value: BigNumberish): Uint8Array {
let bytes = toArray(value); let bytes = toArray(value);
if (bytes.length > WordSize) { if (bytes.length > WordSize) {
logger.throwError("value out-of-bounds", "BUFFER_OVERRUN", { throwError("value out-of-bounds", "BUFFER_OVERRUN", {
buffer: bytes, buffer: bytes,
length: WordSize, length: WordSize,
offset: bytes.length offset: bytes.length
@ -161,7 +161,7 @@ function getValue(value: BigNumberish): Uint8Array {
} }
if (bytes.length !== WordSize) { if (bytes.length !== WordSize) {
bytes = logger.getBytesCopy(concat([ Padding.slice(bytes.length % WordSize), bytes ])); bytes = getBytesCopy(concat([ Padding.slice(bytes.length % WordSize), bytes ]));
} }
return bytes; return bytes;
@ -194,7 +194,7 @@ export abstract class Coder {
} }
_throwError(message: string, value: any): never { _throwError(message: string, value: any): never {
return logger.throwArgumentError(message, this.localName, value); return throwArgumentError(message, this.localName, value);
} }
abstract encode(writer: Writer, value: any): number; abstract encode(writer: Writer, value: any): number;
@ -225,15 +225,15 @@ export class Writer {
} }
appendWriter(writer: Writer): number { appendWriter(writer: Writer): number {
return this.#writeData(logger.getBytesCopy(writer.data)); return this.#writeData(getBytesCopy(writer.data));
} }
// Arrayish item; pad on the right to *nearest* WordSize // Arrayish item; pad on the right to *nearest* WordSize
writeBytes(value: BytesLike): number { writeBytes(value: BytesLike): number {
let bytes = logger.getBytesCopy(value); let bytes = getBytesCopy(value);
const paddingOffset = bytes.length % WordSize; const paddingOffset = bytes.length % WordSize;
if (paddingOffset) { if (paddingOffset) {
bytes = logger.getBytesCopy(concat([ bytes, Padding.slice(paddingOffset) ])) bytes = getBytesCopy(concat([ bytes, Padding.slice(paddingOffset) ]))
} }
return this.#writeData(bytes); return this.#writeData(bytes);
} }
@ -268,7 +268,7 @@ export class Reader {
constructor(data: BytesLike, allowLoose?: boolean) { constructor(data: BytesLike, allowLoose?: boolean) {
defineProperties<Reader>(this, { allowLoose: !!allowLoose }); defineProperties<Reader>(this, { allowLoose: !!allowLoose });
this.#data = logger.getBytesCopy(data); this.#data = getBytesCopy(data);
this.#offset = 0; this.#offset = 0;
} }
@ -284,8 +284,8 @@ export class Reader {
if (this.allowLoose && loose && this.#offset + length <= this.#data.length) { if (this.allowLoose && loose && this.#offset + length <= this.#data.length) {
alignedLength = length; alignedLength = length;
} else { } else {
logger.throwError("data out-of-bounds", "BUFFER_OVERRUN", { throwError("data out-of-bounds", "BUFFER_OVERRUN", {
buffer: logger.getBytesCopy(this.#data), buffer: getBytesCopy(this.#data),
length: this.#data.length, length: this.#data.length,
offset: this.#offset + alignedLength offset: this.#offset + alignedLength
}); });

@ -1,8 +1,9 @@
import { defineProperties } from "../../utils/properties.js"; import {
import { isError } from "../../utils/errors.js"; defineProperties, isError, assertArgumentCount, throwArgumentError, throwError
import { logger } from "../../utils/logger.js"; } from "../../utils/index.js";
import { Typed } from "../typed.js"; import { Typed } from "../typed.js";
import { Coder, Result, WordSize, Writer } from "./abstract-coder.js"; import { Coder, Result, WordSize, Writer } from "./abstract-coder.js";
import { AnonymousCoder } from "./anonymous.js"; import { AnonymousCoder } from "./anonymous.js";
@ -21,7 +22,7 @@ export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array
arrayValues = coders.map((coder) => { arrayValues = coders.map((coder) => {
const name = coder.localName; const name = coder.localName;
if (!name) { if (!name) {
logger.throwError("cannot encode object for signature with missing names", "INVALID_ARGUMENT", { throwError("cannot encode object for signature with missing names", "INVALID_ARGUMENT", {
argument: "values", argument: "values",
info: { coder }, info: { coder },
value: values value: values
@ -29,7 +30,7 @@ export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array
} }
if (unique[name]) { if (unique[name]) {
logger.throwError("cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", { throwError("cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", {
argument: "values", argument: "values",
info: { coder }, info: { coder },
value: values value: values
@ -42,11 +43,11 @@ export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array
}); });
} else { } else {
logger.throwArgumentError("invalid tuple value", "tuple", values); throwArgumentError("invalid tuple value", "tuple", values);
} }
if (coders.length !== arrayValues.length) { if (coders.length !== arrayValues.length) {
logger.throwArgumentError("types/value length mismatch", "tuple", values); throwArgumentError("types/value length mismatch", "tuple", values);
} }
let staticWriter = new Writer(); let staticWriter = new Writer();
@ -173,7 +174,7 @@ export class ArrayCoder extends Coder {
writer.writeValue(value.length); writer.writeValue(value.length);
} }
logger.assertArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): "")); assertArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): ""));
let coders = []; let coders = [];
for (let i = 0; i < value.length; i++) { coders.push(this.coder); } for (let i = 0; i < value.length; i++) { coders.push(this.coder); }
@ -192,7 +193,7 @@ export class ArrayCoder extends Coder {
// bytes as a link to the data). This could use a much // bytes as a link to the data). This could use a much
// tighter bound, but we are erroring on the side of safety. // tighter bound, but we are erroring on the side of safety.
if (count * WordSize > reader.dataLength) { if (count * WordSize > reader.dataLength) {
logger.throwError("insufficient data length", "BUFFER_OVERRUN", { throwError("insufficient data length", "BUFFER_OVERRUN", {
buffer: reader.bytes, buffer: reader.bytes,
offset: count * WordSize, offset: count * WordSize,
length: reader.dataLength length: reader.dataLength

@ -1,5 +1,4 @@
import { logger } from "../../utils/logger.js"; import { getBytesCopy, hexlify } from "../../utils/index.js";
import { hexlify } from "../../utils/data.js";
import { Coder } from "./abstract-coder.js"; import { Coder } from "./abstract-coder.js";
@ -16,7 +15,7 @@ export class DynamicBytesCoder extends Coder {
} }
encode(writer: Writer, value: any): number { encode(writer: Writer, value: any): number {
value = logger.getBytesCopy(value); value = getBytesCopy(value);
let length = writer.writeValue(value.length); let length = writer.writeValue(value.length);
length += writer.writeBytes(value); length += writer.writeBytes(value);
return length; return length;

@ -1,7 +1,5 @@
import { logger } from "../../utils/logger.js"; import { defineProperties, getBytesCopy, hexlify } from "../../utils/index.js";
import { hexlify } from "../../utils/data.js";
import { defineProperties } from "../../utils/properties.js";
import { Typed } from "../typed.js"; import { Typed } from "../typed.js";
import { Coder } from "./abstract-coder.js"; import { Coder } from "./abstract-coder.js";
@ -25,7 +23,7 @@ export class FixedBytesCoder extends Coder {
} }
encode(writer: Writer, _value: BytesLike | Typed): number { encode(writer: Writer, _value: BytesLike | Typed): number {
let data = logger.getBytesCopy(Typed.dereference(_value, this.type)); let data = getBytesCopy(Typed.dereference(_value, this.type));
if (data.length !== this.size) { this._throwError("incorrect data length", _value); } if (data.length !== this.size) { this._throwError("incorrect data length", _value); }
return writer.writeBytes(data); return writer.writeBytes(data);
} }

@ -1,7 +1,7 @@
import { fromTwos, mask, toTwos } from "../../utils/maths.js"; import {
import { defineProperties } from "../../utils/properties.js"; defineProperties, fromTwos, getBigInt, mask, toTwos
} from "../../utils/index.js";
import { logger } from "../../utils/logger.js";
import { Typed } from "../typed.js"; import { Typed } from "../typed.js";
import { Coder, WordSize } from "./abstract-coder.js"; import { Coder, WordSize } from "./abstract-coder.js";
@ -30,7 +30,7 @@ export class NumberCoder extends Coder {
} }
encode(writer: Writer, _value: BigNumberish | Typed): number { encode(writer: Writer, _value: BigNumberish | Typed): number {
let value = logger.getBigInt(Typed.dereference(_value, this.type)); let value = getBigInt(Typed.dereference(_value, this.type));
// Check bounds are safe for encoding // Check bounds are safe for encoding
let maxUintValue = mask(BN_MAX_UINT256, WordSize * 8); let maxUintValue = mask(BN_MAX_UINT256, WordSize * 8);

@ -1,5 +1,7 @@
import { logger } from "../utils/logger.js"; import {
import { defineProperties } from "../utils/index.js"; defineProperties, getBigInt, getNumber,
assertPrivate, throwArgumentError, throwError
} from "../utils/index.js";
export interface JsonFragmentType { export interface JsonFragmentType {
@ -76,7 +78,9 @@ const regexNumber = new RegExp("^([0-9]+)");
const regexIdentifier = new RegExp("^([a-zA-Z$_][a-zA-Z0-9$_]*)"); const regexIdentifier = new RegExp("^([a-zA-Z$_][a-zA-Z0-9$_]*)");
const regexType = new RegExp("^(address|bool|bytes([0-9]*)|string|u?int([0-9]*))"); const regexType = new RegExp("^(address|bool|bytes([0-9]*)|string|u?int([0-9]*))");
/**
* @ignore:
*/
export type Token = Readonly<{ export type Token = Readonly<{
// Type of token (e.g. TYPE, KEYWORD, NUMBER, etc) // Type of token (e.g. TYPE, KEYWORD, NUMBER, etc)
type: string; type: string;
@ -268,7 +272,7 @@ export function lex(text: string): TokenString {
if (tokens.length > 0 && tokens[tokens.length - 1].type === "NUMBER") { if (tokens.length > 0 && tokens[tokens.length - 1].type === "NUMBER") {
const value = (tokens.pop() as Token).text; const value = (tokens.pop() as Token).text;
suffix = value + suffix; suffix = value + suffix;
(<Writeable<Token>>(tokens[tokens.length - 1])).value = logger.getNumber(value); (<Writeable<Token>>(tokens[tokens.length - 1])).value = getNumber(value);
} }
if (tokens.length === 0 || tokens[tokens.length - 1].type !== "BRACKET") { if (tokens.length === 0 || tokens[tokens.length - 1].type !== "BRACKET") {
throw new Error("missing opening bracket"); throw new Error("missing opening bracket");
@ -381,7 +385,7 @@ function consumeGas(tokens: TokenString): null | bigint {
if (tokens.peekType("AT")) { if (tokens.peekType("AT")) {
tokens.pop(); tokens.pop();
if (tokens.peekType("NUMBER")) { if (tokens.peekType("NUMBER")) {
return logger.getBigInt(tokens.pop().text); return getBigInt(tokens.pop().text);
} }
throw new Error("invalid gas"); throw new Error("invalid gas");
} }
@ -399,7 +403,7 @@ const regexArrayType = new RegExp(/^(.*)\[([0-9]*)\]$/);
function verifyBasicType(type: string): string { function verifyBasicType(type: string): string {
const match = type.match(regexType); const match = type.match(regexType);
if (!match) { if (!match) {
return logger.throwArgumentError("invalid type", "type", type); return throwArgumentError("invalid type", "type", type);
} }
if (type === "uint") { return "uint256"; } if (type === "uint") { return "uint256"; }
if (type === "int") { return "int256"; } if (type === "int") { return "int256"; }
@ -408,14 +412,14 @@ function verifyBasicType(type: string): string {
// bytesXX // bytesXX
const length = parseInt(match[2]); const length = parseInt(match[2]);
if (length === 0 || length > 32) { if (length === 0 || length > 32) {
logger.throwArgumentError("invalid bytes length", "type", type); throwArgumentError("invalid bytes length", "type", type);
} }
} else if (match[3]) { } else if (match[3]) {
// intXX or uintXX // intXX or uintXX
const size = parseInt(match[3] as string); const size = parseInt(match[3] as string);
if (size === 0 || size > 256 || size % 8) { if (size === 0 || size > 256 || size % 8) {
logger.throwArgumentError("invalid numeric width", "type", type); throwArgumentError("invalid numeric width", "type", type);
} }
} }
@ -470,7 +474,7 @@ export class ParamType {
constructor(guard: any, name: string, type: string, baseType: string, indexed: null | boolean, components: null | ReadonlyArray<ParamType>, arrayLength: null | number, arrayChildren: null | ParamType) { constructor(guard: any, name: string, type: string, baseType: string, indexed: null | boolean, components: null | ReadonlyArray<ParamType>, arrayLength: null | number, arrayChildren: null | ParamType) {
logger.assertPrivate(guard, _guard, "ParamType"); assertPrivate(guard, _guard, "ParamType");
Object.defineProperty(this, internal, { value: ParamTypeInternal }); Object.defineProperty(this, internal, { value: ParamTypeInternal });
if (components) { components = Object.freeze(components.slice()); } if (components) { components = Object.freeze(components.slice()); }
@ -500,7 +504,7 @@ export class ParamType {
// - full: "tuple(uint256 foo, address bar) indexed baz" // - full: "tuple(uint256 foo, address bar) indexed baz"
format(format: FormatType = FormatType.sighash): string { format(format: FormatType = FormatType.sighash): string {
if (!FormatType[format]) { if (!FormatType[format]) {
logger.throwArgumentError("invalid format type", "format", format); throwArgumentError("invalid format type", "format", format);
} }
if (format === FormatType.json) { if (format === FormatType.json) {
@ -656,13 +660,13 @@ export class ParamType {
const name = obj.name; const name = obj.name;
if (name && (typeof(name) !== "string" || !name.match(regexIdentifier))) { if (name && (typeof(name) !== "string" || !name.match(regexIdentifier))) {
logger.throwArgumentError("invalid name", "obj.name", name); throwArgumentError("invalid name", "obj.name", name);
} }
let indexed = obj.indexed; let indexed = obj.indexed;
if (indexed != null) { if (indexed != null) {
if (!allowIndexed) { if (!allowIndexed) {
logger.throwArgumentError("parameter cannot be indexed", "obj.indexed", obj.indexed); throwArgumentError("parameter cannot be indexed", "obj.indexed", obj.indexed);
} }
indexed = !!indexed; indexed = !!indexed;
} }
@ -756,7 +760,7 @@ export abstract class Fragment {
readonly inputs!: ReadonlyArray<ParamType>; readonly inputs!: ReadonlyArray<ParamType>;
constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>) { constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>) {
logger.assertPrivate(guard, _guard, "Fragment"); assertPrivate(guard, _guard, "Fragment");
inputs = Object.freeze(inputs.slice()); inputs = Object.freeze(inputs.slice());
defineProperties<Fragment>(this, { type, inputs }); defineProperties<Fragment>(this, { type, inputs });
} }
@ -864,7 +868,7 @@ export class ErrorFragment extends NamedFragment {
format(format: FormatType = FormatType.sighash): string { format(format: FormatType = FormatType.sighash): string {
if (!FormatType[format]) { if (!FormatType[format]) {
logger.throwArgumentError("invalid format type", "format", format); throwArgumentError("invalid format type", "format", format);
} }
if (format === FormatType.json) { if (format === FormatType.json) {
@ -905,7 +909,7 @@ export class EventFragment extends NamedFragment {
format(format: FormatType = FormatType.sighash): string { format(format: FormatType = FormatType.sighash): string {
if (!FormatType[format]) { if (!FormatType[format]) {
logger.throwArgumentError("invalid format type", "format", format); throwArgumentError("invalid format type", "format", format);
} }
if (format === FormatType.json) { if (format === FormatType.json) {
@ -950,11 +954,11 @@ export class ConstructorFragment extends Fragment {
format(format: FormatType = FormatType.sighash): string { format(format: FormatType = FormatType.sighash): string {
if (!FormatType[format]) { if (!FormatType[format]) {
logger.throwArgumentError("invalid format type", "format", format); throwArgumentError("invalid format type", "format", format);
} }
if (format === FormatType.sighash) { if (format === FormatType.sighash) {
logger.throwError("cannot format a constructor for sighash", "UNSUPPORTED_OPERATION", { throwError("cannot format a constructor for sighash", "UNSUPPORTED_OPERATION", {
operation: "format(sighash)" operation: "format(sighash)"
}); });
} }
@ -1012,7 +1016,7 @@ export class FunctionFragment extends NamedFragment {
format(format: FormatType = FormatType.sighash): string { format(format: FormatType = FormatType.sighash): string {
if (!FormatType[format]) { if (!FormatType[format]) {
logger.throwArgumentError("invalid format type", "format", format); throwArgumentError("invalid format type", "format", format);
} }
if (format === FormatType.json) { if (format === FormatType.json) {

@ -1,9 +1,10 @@
import { concat, dataSlice, hexlify, zeroPadValue, isHexString } from "../utils/data.js";
import { keccak256 } from "../crypto/index.js" import { keccak256 } from "../crypto/index.js"
import { id } from "../hash/index.js" import { id } from "../hash/index.js"
import { logger } from "../utils/logger.js"; import {
import { defineProperties } from "../utils/properties.js"; concat, dataSlice, getBigInt, getBytes, getBytesCopy, hexlify,
import { toHex } from "../utils/maths.js"; zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex,
throwError, makeError
} from "../utils/index.js";
import { AbiCoder, defaultAbiCoder } from "./abi-coder.js"; import { AbiCoder, defaultAbiCoder } from "./abi-coder.js";
import { checkResultErrors, Result } from "./coders/abstract-coder.js"; import { checkResultErrors, Result } from "./coders/abstract-coder.js";
@ -182,7 +183,7 @@ export class Interface {
switch (fragment.type) { switch (fragment.type) {
case "constructor": case "constructor":
if (this.deploy) { if (this.deploy) {
logger.warn("duplicate definition - constructor"); console.log("duplicate definition - constructor");
return; return;
} }
//checkNames(fragment, "input", fragment.inputs); //checkNames(fragment, "input", fragment.inputs);
@ -208,11 +209,9 @@ export class Interface {
return; return;
} }
// Two identical entries; ignore it
const signature = fragment.format(); const signature = fragment.format();
if (bucket.has(signature)) { if (bucket.has(signature)) { return; }
logger.warn("duplicate definition - " + signature);
return;
}
bucket.set(signature, fragment); bucket.set(signature, fragment);
}); });
@ -228,7 +227,7 @@ export class Interface {
format(format?: FormatType): string | Array<string> { format(format?: FormatType): string | Array<string> {
if (!format) { format = FormatType.full; } if (!format) { format = FormatType.full; }
if (format === FormatType.sighash) { if (format === FormatType.sighash) {
logger.throwArgumentError("interface does not support formatting sighash", "format", format); throwArgumentError("interface does not support formatting sighash", "format", format);
} }
const abi = this.fragments.map((f) => f.format(format)); const abi = this.fragments.map((f) => f.format(format));
@ -266,7 +265,7 @@ export class Interface {
for (const fragment of this.#functions.values()) { for (const fragment of this.#functions.values()) {
if (selector === this.getSelector(fragment)) { return fragment; } if (selector === this.getSelector(fragment)) { return fragment; }
} }
logger.throwArgumentError("no matching function", "selector", key); throwArgumentError("no matching function", "selector", key);
} }
// It is a bare name, look up the function (will return null if ambiguous) // It is a bare name, look up the function (will return null if ambiguous)
@ -328,11 +327,11 @@ export class Interface {
} }
if (matching.length === 0) { if (matching.length === 0) {
logger.throwArgumentError("no matching function", "name", key); throwArgumentError("no matching function", "name", key);
} else if (matching.length > 1 && forceUnique) { } else if (matching.length > 1 && forceUnique) {
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", "); const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", ");
logger.throwArgumentError(`multiple matching functions (i.e. ${ matchStr })`, "name", key); throwArgumentError(`multiple matching functions (i.e. ${ matchStr })`, "name", key);
} }
return matching[0]; return matching[0];
@ -342,7 +341,7 @@ export class Interface {
const result = this.#functions.get(FunctionFragment.fromString(key).format()); const result = this.#functions.get(FunctionFragment.fromString(key).format());
if (result) { return result; } if (result) { return result; }
return logger.throwArgumentError("no matching function", "signature", key); return throwArgumentError("no matching function", "signature", key);
} }
getFunctionName(key: string): string { getFunctionName(key: string): string {
return (this.#getFunction(key, null, false)).name; return (this.#getFunction(key, null, false)).name;
@ -361,7 +360,7 @@ export class Interface {
for (const fragment of this.#events.values()) { for (const fragment of this.#events.values()) {
if (eventTopic === this.getEventTopic(fragment)) { return fragment; } if (eventTopic === this.getEventTopic(fragment)) { return fragment; }
} }
logger.throwArgumentError("no matching event", "eventTopic", key); throwArgumentError("no matching event", "eventTopic", key);
} }
// It is a bare name, look up the function (will return null if ambiguous) // It is a bare name, look up the function (will return null if ambiguous)
@ -396,10 +395,10 @@ export class Interface {
} }
if (matching.length === 0) { if (matching.length === 0) {
logger.throwArgumentError("no matching event", "name", key); throwArgumentError("no matching event", "name", key);
} else if (matching.length > 1 && forceUnique) { } else if (matching.length > 1 && forceUnique) {
// @TODO: refine by Typed // @TODO: refine by Typed
logger.throwArgumentError("multiple matching events", "name", key); throwArgumentError("multiple matching events", "name", key);
} }
return matching[0]; return matching[0];
@ -409,7 +408,7 @@ export class Interface {
const result = this.#events.get(EventFragment.fromString(key).format()); const result = this.#events.get(EventFragment.fromString(key).format());
if (result) { return result; } if (result) { return result; }
return logger.throwArgumentError("no matching event", "signature", key); return throwArgumentError("no matching event", "signature", key);
} }
getEventName(key: string): string { getEventName(key: string): string {
return (this.#getEvent(key, null, false)).name; return (this.#getEvent(key, null, false)).name;
@ -430,7 +429,7 @@ export class Interface {
for (const fragment of this.#errors.values()) { for (const fragment of this.#errors.values()) {
if (selector === this.getSelector(fragment)) { return fragment; } if (selector === this.getSelector(fragment)) { return fragment; }
} }
logger.throwArgumentError("no matching error", "selector", key); throwArgumentError("no matching error", "selector", key);
} }
// It is a bare name, look up the function (will return null if ambiguous) // It is a bare name, look up the function (will return null if ambiguous)
@ -443,10 +442,10 @@ export class Interface {
if (matching.length === 0) { if (matching.length === 0) {
if (key === "Error") { return ErrorFragment.fromString("error Error(string)"); } if (key === "Error") { return ErrorFragment.fromString("error Error(string)"); }
if (key === "Panic") { return ErrorFragment.fromString("error Panic(uint256)"); } if (key === "Panic") { return ErrorFragment.fromString("error Panic(uint256)"); }
logger.throwArgumentError("no matching error", "name", key); throwArgumentError("no matching error", "name", key);
} else if (matching.length > 1) { } else if (matching.length > 1) {
// @TODO: refine by Typed // @TODO: refine by Typed
logger.throwArgumentError("multiple matching errors", "name", key); throwArgumentError("multiple matching errors", "name", key);
} }
return matching[0]; return matching[0];
@ -460,7 +459,7 @@ export class Interface {
const result = this.#errors.get(key); const result = this.#errors.get(key);
if (result) { return result; } if (result) { return result; }
return logger.throwArgumentError("no matching error", "signature", key); return throwArgumentError("no matching error", "signature", key);
} }
// Get the 4-byte selector used by Solidity to identify a function // Get the 4-byte selector used by Solidity to identify a function
@ -508,7 +507,7 @@ export class Interface {
if (typeof(fragment) === "string") { fragment = this.getError(fragment); } if (typeof(fragment) === "string") { fragment = this.getError(fragment); }
if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) { if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) {
logger.throwArgumentError(`data signature does not match error ${ fragment.name }.`, "data", data); throwArgumentError(`data signature does not match error ${ fragment.name }.`, "data", data);
} }
return this._decodeParams(fragment.inputs, dataSlice(data, 4)); return this._decodeParams(fragment.inputs, dataSlice(data, 4));
@ -528,7 +527,7 @@ export class Interface {
if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); } if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); }
if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) { if (dataSlice(data, 0, 4) !== this.getSelector(fragment)) {
logger.throwArgumentError(`data signature does not match function ${ fragment.name }.`, "data", data); throwArgumentError(`data signature does not match function ${ fragment.name }.`, "data", data);
} }
return this._decodeParams(fragment.inputs, dataSlice(data, 4)); return this._decodeParams(fragment.inputs, dataSlice(data, 4));
@ -550,7 +549,7 @@ export class Interface {
let message = "invalid length for result data"; let message = "invalid length for result data";
const bytes = logger.getBytesCopy(data); const bytes = getBytesCopy(data);
if ((bytes.length % 32) === 0) { if ((bytes.length % 32) === 0) {
try { try {
return this.#abiCoder.decode(fragment.outputs, bytes); return this.#abiCoder.decode(fragment.outputs, bytes);
@ -560,7 +559,7 @@ export class Interface {
} }
// Call returned data with no error, but the data is junk // Call returned data with no error, but the data is junk
return logger.throwError(message, "BAD_DATA", { return throwError(message, "BAD_DATA", {
value: hexlify(bytes), value: hexlify(bytes),
info: { method: fragment.name, signature: fragment.format() } info: { method: fragment.name, signature: fragment.format() }
}); });
@ -569,7 +568,7 @@ export class Interface {
makeError(fragment: FunctionFragment | string, _data: BytesLike, tx?: { data: string }): Error { makeError(fragment: FunctionFragment | string, _data: BytesLike, tx?: { data: string }): Error {
if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); } if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); }
const data = logger.getBytes(_data); const data = getBytes(_data);
let args: undefined | Result = undefined; let args: undefined | Result = undefined;
if (tx) { if (tx) {
@ -616,7 +615,7 @@ export class Interface {
} }
} }
return logger.makeError("call revert exception", "CALL_EXCEPTION", { return makeError("call revert exception", "CALL_EXCEPTION", {
data: hexlify(data), transaction: null, data: hexlify(data), transaction: null,
method: fragment.name, signature: fragment.format(), args, method: fragment.name, signature: fragment.format(), args,
errorArgs, errorName, errorSignature, reason errorArgs, errorName, errorSignature, reason
@ -668,7 +667,7 @@ export class Interface {
} }
if (values.length > eventFragment.inputs.length) { if (values.length > eventFragment.inputs.length) {
logger.throwError("too many arguments for " + eventFragment.format(), "UNEXPECTED_ARGUMENT", { throwError("too many arguments for " + eventFragment.format(), "UNEXPECTED_ARGUMENT", {
count: values.length, count: values.length,
expectedCount: eventFragment.inputs.length expectedCount: eventFragment.inputs.length
}) })
@ -705,7 +704,7 @@ export class Interface {
if (!param.indexed) { if (!param.indexed) {
if (value != null) { if (value != null) {
logger.throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value); throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value);
} }
return; return;
} }
@ -713,7 +712,7 @@ export class Interface {
if (value == null) { if (value == null) {
topics.push(null); topics.push(null);
} else if (param.baseType === "array" || param.baseType === "tuple") { } else if (param.baseType === "array" || param.baseType === "tuple") {
logger.throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value); throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value);
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
topics.push(value.map((value) => encodeTopic(param, value))); topics.push(value.map((value) => encodeTopic(param, value)));
} else { } else {
@ -744,7 +743,7 @@ export class Interface {
} }
if (values.length !== eventFragment.inputs.length) { if (values.length !== eventFragment.inputs.length) {
logger.throwArgumentError("event arguments/values mismatch", "values", values); throwArgumentError("event arguments/values mismatch", "values", values);
} }
eventFragment.inputs.forEach((param, index) => { eventFragment.inputs.forEach((param, index) => {
@ -781,7 +780,7 @@ export class Interface {
if (topics != null && !eventFragment.anonymous) { if (topics != null && !eventFragment.anonymous) {
const eventTopic = this.getEventTopic(eventFragment); const eventTopic = this.getEventTopic(eventFragment);
if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) { if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== eventTopic) {
logger.throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]); throwArgumentError("fragment/topic mismatch", "topics[0]", topics[0]);
} }
topics = topics.slice(1); topics = topics.slice(1);
} }
@ -846,8 +845,8 @@ export class Interface {
// Given a transaction, find the matching function fragment (if any) and // Given a transaction, find the matching function fragment (if any) and
// determine all its properties and call parameters // determine all its properties and call parameters
parseTransaction(tx: { data: string, value?: BigNumberish }): null | TransactionDescription { parseTransaction(tx: { data: string, value?: BigNumberish }): null | TransactionDescription {
const data = logger.getBytes(tx.data, "tx.data"); const data = getBytes(tx.data, "tx.data");
const value = logger.getBigInt((tx.value != null) ? tx.value: 0, "tx.value"); const value = getBigInt((tx.value != null) ? tx.value: 0, "tx.value");
const fragment = this.getFunction(hexlify(data.slice(0, 4))); const fragment = this.getFunction(hexlify(data.slice(0, 4)));
@ -887,7 +886,7 @@ export class Interface {
} }
static from(value: ReadonlyArray<Fragment | string | JsonFragment> | string | Interface) { static from(value: ReadonlyArray<Fragment | string | JsonFragment> | string | Interface): Interface {
// Already an Interface, which is immutable // Already an Interface, which is immutable
if (value instanceof Interface) { return value; } if (value instanceof Interface) { return value; }

@ -1,5 +1,5 @@
import { keccak256 } from "../crypto/keccak.js"; import { keccak256 } from "../crypto/keccak.js";
import { logger } from "../utils/logger.js"; import { getBytes, throwArgumentError } from "../utils/index.js";
const BN_0 = BigInt(0); const BN_0 = BigInt(0);
@ -19,7 +19,7 @@ function getChecksumAddress(address: string): string {
expanded[i] = chars[i].charCodeAt(0); expanded[i] = chars[i].charCodeAt(0);
} }
const hashed = logger.getBytes(keccak256(expanded)); const hashed = getBytes(keccak256(expanded));
for (let i = 0; i < 40; i += 2) { for (let i = 0; i < 40; i += 2) {
if ((hashed[i >> 1] >> 4) >= 8) { if ((hashed[i >> 1] >> 4) >= 8) {
@ -84,7 +84,7 @@ function fromBase36(value: string): bigint {
export function getAddress(address: string): string { export function getAddress(address: string): string {
if (typeof(address) !== "string") { if (typeof(address) !== "string") {
logger.throwArgumentError("invalid address", "address", address); throwArgumentError("invalid address", "address", address);
} }
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) { if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
@ -96,7 +96,7 @@ export function getAddress(address: string): string {
// It is a checksummed address with a bad checksum // It is a checksummed address with a bad checksum
if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) { if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
logger.throwArgumentError("bad address checksum", "address", address); throwArgumentError("bad address checksum", "address", address);
} }
return result; return result;
@ -106,7 +106,7 @@ export function getAddress(address: string): string {
if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) { if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
// It is an ICAP address with a bad checksum // It is an ICAP address with a bad checksum
if (address.substring(2, 4) !== ibanChecksum(address)) { if (address.substring(2, 4) !== ibanChecksum(address)) {
logger.throwArgumentError("bad icap checksum", "address", address); throwArgumentError("bad icap checksum", "address", address);
} }
let result = fromBase36(address.substring(4)).toString(16); let result = fromBase36(address.substring(4)).toString(16);
@ -114,7 +114,7 @@ export function getAddress(address: string): string {
return getChecksumAddress("0x" + result); return getChecksumAddress("0x" + result);
} }
return logger.throwArgumentError("invalid address", "address", address); return throwArgumentError("invalid address", "address", address);
} }
export function getIcapAddress(address: string): string { export function getIcapAddress(address: string): string {

@ -1,4 +1,4 @@
import { logger } from "../utils/logger.js"; import { throwArgumentError, throwError } from "../utils/index.js";
import { getAddress } from "./address.js"; import { getAddress } from "./address.js";
@ -21,9 +21,9 @@ async function checkAddress(target: any, promise: Promise<null | string>): Promi
const result = await promise; const result = await promise;
if (result == null || result === "0x0000000000000000000000000000000000000000") { if (result == null || result === "0x0000000000000000000000000000000000000000") {
if (typeof(target) === "string") { if (typeof(target) === "string") {
return logger.throwError("unconfigured name", "UNCONFIGURED_NAME", { value: target }); return throwError("unconfigured name", "UNCONFIGURED_NAME", { value: target });
} }
return logger.throwArgumentError("invalid AddressLike value; did not resolve to a value address", "target", target); return throwArgumentError("invalid AddressLike value; did not resolve to a value address", "target", target);
} }
return getAddress(result); return getAddress(result);
} }
@ -36,7 +36,7 @@ export function resolveAddress(target: AddressLike, resolver?: null | NameResolv
if (target.match(/^0x[0-9a-f]{40}$/i)) { return getAddress(target); } if (target.match(/^0x[0-9a-f]{40}$/i)) { return getAddress(target); }
if (resolver == null) { if (resolver == null) {
return logger.throwError("ENS resolution requires a provider", "UNSUPPORTED_OPERATION", { return throwError("ENS resolution requires a provider", "UNSUPPORTED_OPERATION", {
operation: "resolveName", operation: "resolveName",
}); });
} }
@ -50,5 +50,5 @@ export function resolveAddress(target: AddressLike, resolver?: null | NameResolv
return checkAddress(target, target); return checkAddress(target, target);
} }
return logger.throwArgumentError("unsupported addressable value", "target", target); return throwArgumentError("unsupported addressable value", "target", target);
} }

@ -1,5 +1,7 @@
import { keccak256 } from "../crypto/keccak.js"; import { keccak256 } from "../crypto/keccak.js";
import { concat, dataSlice, encodeRlp, logger } from "../utils/index.js"; import {
concat, dataSlice, getBigInt, getBytes, encodeRlp, throwArgumentError
} from "../utils/index.js";
import { getAddress } from "./address.js"; import { getAddress } from "./address.js";
@ -9,7 +11,7 @@ import type { BigNumberish, BytesLike } from "../utils/index.js";
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed // http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): string { export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): string {
const from = getAddress(tx.from); const from = getAddress(tx.from);
const nonce = logger.getBigInt(tx.nonce, "tx.nonce"); const nonce = getBigInt(tx.nonce, "tx.nonce");
let nonceHex = nonce.toString(16); let nonceHex = nonce.toString(16);
if (nonceHex === "0") { if (nonceHex === "0") {
@ -25,15 +27,15 @@ export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): str
export function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash: BytesLike): string { export function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash: BytesLike): string {
const from = getAddress(_from); const from = getAddress(_from);
const salt = logger.getBytes(_salt, "salt"); const salt = getBytes(_salt, "salt");
const initCodeHash = logger.getBytes(_initCodeHash, "initCodeHash"); const initCodeHash = getBytes(_initCodeHash, "initCodeHash");
if (salt.length !== 32) { if (salt.length !== 32) {
logger.throwArgumentError("salt must be 32 bytes", "salt", _salt); throwArgumentError("salt must be 32 bytes", "salt", _salt);
} }
if (initCodeHash.length !== 32) { if (initCodeHash.length !== 32) {
logger.throwArgumentError("initCodeHash must be 32 bytes", "initCodeHash", _initCodeHash); throwArgumentError("initCodeHash must be 32 bytes", "initCodeHash", _initCodeHash);
} }
return getAddress(dataSlice(keccak256(concat([ "0xff", from, salt, initCodeHash ])), 12)) return getAddress(dataSlice(keccak256(concat([ "0xff", from, salt, initCodeHash ])), 12))

@ -1,9 +1,10 @@
import { resolveAddress } from "../address/index.js";
import { Interface, Typed } from "../abi/index.js"; import { Interface, Typed } from "../abi/index.js";
import { import { resolveAddress } from "../address/index.js";
defineProperties, isCallException, isHexString, resolveProperties, logger
} from "../utils/index.js";
import { copyRequest, Log, TransactionResponse } from "../providers/index.js"; import { copyRequest, Log, TransactionResponse } from "../providers/index.js";
import {
defineProperties, isCallException, isHexString, resolveProperties,
makeError, throwArgumentError, throwError
} from "../utils/index.js";
import { import {
ContractEventPayload, ContractEventPayload,
@ -132,9 +133,9 @@ export async function copyOverrides(arg: any): Promise<Omit<ContractTransaction,
// Some sanity checking; these are what these methods adds // Some sanity checking; these are what these methods adds
//if ((<any>overrides).to) { //if ((<any>overrides).to) {
if (overrides.to) { if (overrides.to) {
logger.throwArgumentError("cannot override to", "overrides.to", overrides.to); throwArgumentError("cannot override to", "overrides.to", overrides.to);
} else if (overrides.data) { } else if (overrides.data) {
logger.throwArgumentError("cannot override data", "overrides.data", overrides.data); throwArgumentError("cannot override data", "overrides.data", overrides.data);
} }
// Resolve any from // Resolve any from
@ -223,7 +224,7 @@ class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | Co
async send(...args: ContractMethodArgs<A>): Promise<ContractTransactionResponse> { async send(...args: ContractMethodArgs<A>): Promise<ContractTransactionResponse> {
const runner = this._contract.runner; const runner = this._contract.runner;
if (!canSend(runner)) { if (!canSend(runner)) {
return logger.throwError("contract runner does not support sending transactions", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not support sending transactions", "UNSUPPORTED_OPERATION", {
operation: "sendTransaction" operation: "sendTransaction"
}); });
} }
@ -235,7 +236,7 @@ class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | Co
async estimateGas(...args: ContractMethodArgs<A>): Promise<bigint> { async estimateGas(...args: ContractMethodArgs<A>): Promise<bigint> {
const runner = getRunner(this._contract.runner, "estimateGas"); const runner = getRunner(this._contract.runner, "estimateGas");
if (!canEstimate(runner)) { if (!canEstimate(runner)) {
return logger.throwError("contract runner does not support gas estimation", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not support gas estimation", "UNSUPPORTED_OPERATION", {
operation: "estimateGas" operation: "estimateGas"
}); });
} }
@ -245,7 +246,7 @@ class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | Co
async staticCallResult(...args: ContractMethodArgs<A>): Promise<Result> { async staticCallResult(...args: ContractMethodArgs<A>): Promise<Result> {
const runner = getRunner(this._contract.runner, "call"); const runner = getRunner(this._contract.runner, "call");
if (!canCall(runner)) { if (!canCall(runner)) {
return logger.throwError("contract runner does not support calling", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not support calling", "UNSUPPORTED_OPERATION", {
operation: "call" operation: "call"
}); });
} }
@ -397,7 +398,7 @@ async function getSub(contract: BaseContract, event: ContractEventName): Promise
// Make sure our runner can actually subscribe to events // Make sure our runner can actually subscribe to events
const provider = getProvider(contract.runner); const provider = getProvider(contract.runner);
if (!provider) { if (!provider) {
return logger.throwError("contract runner does not support subscribing", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not support subscribing", "UNSUPPORTED_OPERATION", {
operation: "on" operation: "on"
}); });
} }
@ -504,7 +505,7 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
} else { } else {
const resolver = getRunner(runner, "resolveName"); const resolver = getRunner(runner, "resolveName");
if (!canResolve(resolver)) { if (!canResolve(resolver)) {
throw logger.makeError("contract runner does not support name resolution", "UNSUPPORTED_OPERATION", { throw makeError("contract runner does not support name resolution", "UNSUPPORTED_OPERATION", {
operation: "resolveName" operation: "resolveName"
}); });
} }
@ -566,7 +567,7 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
async getDeployedCode(): Promise<null | string> { async getDeployedCode(): Promise<null | string> {
const provider = getProvider(this.runner); const provider = getProvider(this.runner);
if (!provider) { if (!provider) {
return logger.throwError("runner does not support .provider", "UNSUPPORTED_OPERATION", { return throwError("runner does not support .provider", "UNSUPPORTED_OPERATION", {
operation: "getDeployedCode" operation: "getDeployedCode"
}); });
} }
@ -591,7 +592,7 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
// Make sure we can subscribe to a provider event // Make sure we can subscribe to a provider event
const provider = getProvider(this.runner); const provider = getProvider(this.runner);
if (provider == null) { if (provider == null) {
return logger.throwError("contract runner does not support .provider", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not support .provider", "UNSUPPORTED_OPERATION", {
operation: "waitForDeployment" operation: "waitForDeployment"
}); });
} }
@ -637,7 +638,7 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
const provider = getProvider(this.runner); const provider = getProvider(this.runner);
if (!provider) { if (!provider) {
return logger.throwError("contract runner does not have a provider", "UNSUPPORTED_OPERATION", { return throwError("contract runner does not have a provider", "UNSUPPORTED_OPERATION", {
operation: "queryFilter" operation: "queryFilter"
}); });
} }

@ -1,7 +1,10 @@
import { Interface } from "../abi/index.js"; import { Interface } from "../abi/index.js";
import { getCreateAddress } from "../address/index.js"; import { getCreateAddress } from "../address/index.js";
import { concat, defineProperties, hexlify, logger } from "../utils/index.js"; import {
concat, defineProperties, getBytes, hexlify,
throwArgumentError, throwError
} from "../utils/index.js";
import { BaseContract, copyOverrides, resolveArgs } from "./contract.js"; import { BaseContract, copyOverrides, resolveArgs } from "./contract.js";
@ -27,11 +30,11 @@ export class ContractFactory<A extends Array<any> = Array<any>, I = BaseContract
// Dereference Solidity bytecode objects and allow a missing `0x`-prefix // Dereference Solidity bytecode objects and allow a missing `0x`-prefix
if (bytecode instanceof Uint8Array) { if (bytecode instanceof Uint8Array) {
bytecode = hexlify(logger.getBytes(bytecode)); bytecode = hexlify(getBytes(bytecode));
} else { } else {
if (typeof(bytecode) === "object") { bytecode = bytecode.object; } if (typeof(bytecode) === "object") { bytecode = bytecode.object; }
if (bytecode.substring(0, 2) !== "0x") { bytecode = "0x" + bytecode; } if (bytecode.substring(0, 2) !== "0x") { bytecode = "0x" + bytecode; }
bytecode = hexlify(logger.getBytes(bytecode)); bytecode = hexlify(getBytes(bytecode));
} }
defineProperties<ContractFactory>(this, { defineProperties<ContractFactory>(this, {
@ -62,7 +65,7 @@ export class ContractFactory<A extends Array<any> = Array<any>, I = BaseContract
const tx = await this.getDeployTransaction(...args); const tx = await this.getDeployTransaction(...args);
if (!this.runner || typeof(this.runner.sendTransaction) !== "function") { if (!this.runner || typeof(this.runner.sendTransaction) !== "function") {
return logger.throwError("factory runner does not support sending transactions", "UNSUPPORTED_OPERATION", { return throwError("factory runner does not support sending transactions", "UNSUPPORTED_OPERATION", {
operation: "sendTransaction" operation: "sendTransaction"
}); });
} }
@ -78,7 +81,7 @@ export class ContractFactory<A extends Array<any> = Array<any>, I = BaseContract
static fromSolidity<A extends Array<any> = Array<any>, I = ContractInterface>(output: any, runner?: ContractRunner): ContractFactory<A, I> { static fromSolidity<A extends Array<any> = Array<any>, I = ContractInterface>(output: any, runner?: ContractRunner): ContractFactory<A, I> {
if (output == null) { if (output == null) {
logger.throwArgumentError("bad compiler output", "output", output); throwArgumentError("bad compiler output", "output", output);
} }
if (typeof(output) === "string") { output = JSON.parse(output); } if (typeof(output) === "string") { output = JSON.parse(output); }

@ -5,7 +5,7 @@ import { pbkdf2 } from "@noble/hashes/pbkdf2";
import { sha256 } from "@noble/hashes/sha256"; import { sha256 } from "@noble/hashes/sha256";
import { sha512 } from "@noble/hashes/sha512"; import { sha512 } from "@noble/hashes/sha512";
import { logger } from "../utils/logger.js"; import { throwArgumentError, throwError } from "../utils/index.js";
declare global { declare global {
@ -37,13 +37,13 @@ export function createHash(algo: string): CryptoHasher {
case "sha256": return sha256.create(); case "sha256": return sha256.create();
case "sha512": return sha512.create(); case "sha512": return sha512.create();
} }
return logger.throwArgumentError("invalid hashing algorithm name", "algorithm", algo); return throwArgumentError("invalid hashing algorithm name", "algorithm", algo);
} }
export function createHmac(_algo: string, key: Uint8Array): CryptoHasher { export function createHmac(_algo: string, key: Uint8Array): CryptoHasher {
const algo = ({ sha256, sha512 }[_algo]); const algo = ({ sha256, sha512 }[_algo]);
if (algo == null) { if (algo == null) {
return logger.throwArgumentError("invalid hmac algorithm", "algorithm", _algo); return throwArgumentError("invalid hmac algorithm", "algorithm", _algo);
} }
return hmac.create(algo, key); return hmac.create(algo, key);
} }
@ -51,20 +51,20 @@ export function createHmac(_algo: string, key: Uint8Array): CryptoHasher {
export function pbkdf2Sync(password: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, _algo: "sha256" | "sha512"): Uint8Array { export function pbkdf2Sync(password: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, _algo: "sha256" | "sha512"): Uint8Array {
const algo = ({ sha256, sha512 }[_algo]); const algo = ({ sha256, sha512 }[_algo]);
if (algo == null) { if (algo == null) {
return logger.throwArgumentError("invalid pbkdf2 algorithm", "algorithm", _algo); return throwArgumentError("invalid pbkdf2 algorithm", "algorithm", _algo);
} }
return pbkdf2(algo, password, salt, { c: iterations, dkLen: keylen }); return pbkdf2(algo, password, salt, { c: iterations, dkLen: keylen });
} }
export function randomBytes(length: number): Uint8Array { export function randomBytes(length: number): Uint8Array {
if (crypto == null) { if (crypto == null) {
return logger.throwError("platform does not support secure random numbers", "UNSUPPORTED_OPERATION", { return throwError("platform does not support secure random numbers", "UNSUPPORTED_OPERATION", {
operation: "randomBytes" operation: "randomBytes"
}); });
} }
if (!Number.isInteger(length) || length <= 0 || length > 1024) { if (!Number.isInteger(length) || length <= 0 || length > 1024) {
logger.throwArgumentError("invalid length", "length", length); throwArgumentError("invalid length", "length", length);
} }
const result = new Uint8Array(length); const result = new Uint8Array(length);

@ -1,5 +1,5 @@
import { createHmac } from "./crypto.js"; import { createHmac } from "./crypto.js";
import { hexlify, logger } from "../utils/index.js"; import { getBytes, hexlify } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -13,8 +13,8 @@ const _computeHmac = function(algorithm: "sha256" | "sha512", key: Uint8Array, d
let __computeHmac = _computeHmac; let __computeHmac = _computeHmac;
export function computeHmac(algorithm: "sha256" | "sha512", _key: BytesLike, _data: BytesLike): string { export function computeHmac(algorithm: "sha256" | "sha512", _key: BytesLike, _data: BytesLike): string {
const key = logger.getBytes(_key, "key"); const key = getBytes(_key, "key");
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
return hexlify(__computeHmac(algorithm, key, data)); return hexlify(__computeHmac(algorithm, key, data));
} }
computeHmac._ = _computeHmac; computeHmac._ = _computeHmac;

@ -1,6 +1,6 @@
import { keccak_256 } from "@noble/hashes/sha3"; import { keccak_256 } from "@noble/hashes/sha3";
import { hexlify, logger } from "../utils/index.js"; import { getBytes, hexlify } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -14,7 +14,7 @@ const _keccak256 = function(data: Uint8Array): Uint8Array {
let __keccak256: (data: Uint8Array) => BytesLike = _keccak256; let __keccak256: (data: Uint8Array) => BytesLike = _keccak256;
export function keccak256(_data: BytesLike): string { export function keccak256(_data: BytesLike): string {
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
return hexlify(__keccak256(data)); return hexlify(__keccak256(data));
} }
keccak256._ = _keccak256; keccak256._ = _keccak256;

@ -1,6 +1,6 @@
import { pbkdf2Sync } from "./crypto.js"; import { pbkdf2Sync } from "./crypto.js";
import { hexlify, logger } from "../utils/index.js"; import { getBytes, hexlify } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -14,8 +14,8 @@ const _pbkdf2 = function(password: Uint8Array, salt: Uint8Array, iterations: num
let __pbkdf2 = _pbkdf2; let __pbkdf2 = _pbkdf2;
export function pbkdf2(_password: BytesLike, _salt: BytesLike, iterations: number, keylen: number, algo: "sha256" | "sha512"): string { export function pbkdf2(_password: BytesLike, _salt: BytesLike, iterations: number, keylen: number, algo: "sha256" | "sha512"): string {
const password = logger.getBytes(_password, "password"); const password = getBytes(_password, "password");
const salt = logger.getBytes(_salt, "salt"); const salt = getBytes(_salt, "salt");
return hexlify(__pbkdf2(password, salt, iterations, keylen, algo)); return hexlify(__pbkdf2(password, salt, iterations, keylen, algo));
} }
pbkdf2._ = _pbkdf2; pbkdf2._ = _pbkdf2;

@ -1,6 +1,6 @@
import { ripemd160 as noble_ripemd160 } from "@noble/hashes/ripemd160"; import { ripemd160 as noble_ripemd160 } from "@noble/hashes/ripemd160";
import { hexlify, logger } from "../utils/index.js"; import { getBytes, hexlify } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -14,7 +14,7 @@ const _ripemd160 = function(data: Uint8Array): Uint8Array {
let __ripemd160: (data: Uint8Array) => BytesLike = _ripemd160; let __ripemd160: (data: Uint8Array) => BytesLike = _ripemd160;
export function ripemd160(_data: BytesLike): string { export function ripemd160(_data: BytesLike): string {
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
return hexlify(__ripemd160(data)); return hexlify(__ripemd160(data));
} }
ripemd160._ = _ripemd160; ripemd160._ = _ripemd160;

@ -1,6 +1,6 @@
import { scrypt as _nobleSync, scryptAsync as _nobleAsync } from "@noble/hashes/scrypt"; import { scrypt as _nobleSync, scryptAsync as _nobleAsync } from "@noble/hashes/scrypt";
import { hexlify as H, logger } from "../utils/index.js"; import { getBytes, hexlify as H } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -22,8 +22,8 @@ let __scryptSync: (passwd: Uint8Array, salt: Uint8Array, N: number, r: number, p
export async function scrypt(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number, progress?: ProgressCallback): Promise<string> { export async function scrypt(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number, progress?: ProgressCallback): Promise<string> {
const passwd = logger.getBytes(_passwd, "passwd"); const passwd = getBytes(_passwd, "passwd");
const salt = logger.getBytes(_salt, "salt"); const salt = getBytes(_salt, "salt");
return H(await __scryptAsync(passwd, salt, N, r, p, dkLen, progress)); return H(await __scryptAsync(passwd, salt, N, r, p, dkLen, progress));
} }
scrypt._ = _scryptAsync; scrypt._ = _scryptAsync;
@ -35,8 +35,8 @@ scrypt.register = function(func: (passwd: Uint8Array, salt: Uint8Array, N: numbe
Object.freeze(scrypt); Object.freeze(scrypt);
export function scryptSync(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number): string { export function scryptSync(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number): string {
const passwd = logger.getBytes(_passwd, "passwd"); const passwd = getBytes(_passwd, "passwd");
const salt = logger.getBytes(_salt, "salt"); const salt = getBytes(_salt, "salt");
return H(__scryptSync(passwd, salt, N, r, p, dkLen)); return H(__scryptSync(passwd, salt, N, r, p, dkLen));
} }
scryptSync._ = _scryptSync; scryptSync._ = _scryptSync;

@ -1,6 +1,6 @@
import { createHash } from "./crypto.js"; import { createHash } from "./crypto.js";
import { hexlify, logger } from "../utils/index.js"; import { getBytes, hexlify } from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -20,7 +20,7 @@ let locked256 = false, locked512 = false;
export function sha256(_data: BytesLike): string { export function sha256(_data: BytesLike): string {
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
return hexlify(__sha256(data)); return hexlify(__sha256(data));
} }
sha256._ = _sha256; sha256._ = _sha256;
@ -32,7 +32,7 @@ sha256.register = function(func: (data: Uint8Array) => BytesLike): void {
Object.freeze(sha256); Object.freeze(sha256);
export function sha512(_data: BytesLike): string { export function sha512(_data: BytesLike): string {
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
return hexlify(__sha512(data)); return hexlify(__sha512(data));
} }
sha512._ = _sha512; sha512._ = _sha512;

@ -1,9 +1,13 @@
import { ZeroHash } from "../constants/index.js"; import { ZeroHash } from "../constants/index.js";
import { import {
concat, dataLength, getStore, hexlify, isHexString, logger, setStore concat, dataLength, getBigInt, getBytes, getNumber, getStore, hexlify,
isHexString, setStore,
assertPrivate, throwArgumentError
} from "../utils/index.js"; } from "../utils/index.js";
import type { BigNumberish, BytesLike, Freezable, Frozen } from "../utils/index.js"; import type {
BigNumberish, BytesLike, Freezable, Frozen
} from "../utils/index.js";
// Constants // Constants
@ -45,7 +49,7 @@ export class Signature implements Freezable<Signature> {
get r(): string { return getStore(this.#props, "r"); } get r(): string { return getStore(this.#props, "r"); }
set r(value: BytesLike) { set r(value: BytesLike) {
if (dataLength(value) !== 32) { if (dataLength(value) !== 32) {
logger.throwArgumentError("invalid r", "value", value); throwArgumentError("invalid r", "value", value);
} }
setStore(this.#props, "r", hexlify(value)); setStore(this.#props, "r", hexlify(value));
} }
@ -53,16 +57,16 @@ export class Signature implements Freezable<Signature> {
get s(): string { return getStore(this.#props, "s"); } get s(): string { return getStore(this.#props, "s"); }
set s(value: BytesLike) { set s(value: BytesLike) {
if (dataLength(value) !== 32) { if (dataLength(value) !== 32) {
logger.throwArgumentError("invalid r", "value", value); throwArgumentError("invalid r", "value", value);
} else if (logger.getBytes(value)[0] & 0x80) { } else if (getBytes(value)[0] & 0x80) {
logger.throwArgumentError("non-canonical s", "value", value); throwArgumentError("non-canonical s", "value", value);
} }
setStore(this.#props, "s", hexlify(value)); setStore(this.#props, "s", hexlify(value));
} }
get v(): 27 | 28 { return getStore(this.#props, "v"); } get v(): 27 | 28 { return getStore(this.#props, "v"); }
set v(value: BigNumberish) { set v(value: BigNumberish) {
const v = logger.getNumber(value, "value"); const v = getNumber(value, "value");
if (v !== 27 && v !== 28) { throw new Error("@TODO"); } if (v !== 27 && v !== 28) { throw new Error("@TODO"); }
setStore(this.#props, "v", v); setStore(this.#props, "v", v);
} }
@ -89,7 +93,7 @@ export class Signature implements Freezable<Signature> {
get yParityAndS(): string { get yParityAndS(): string {
// The EIP-2098 compact representation // The EIP-2098 compact representation
const yParityAndS = logger.getBytes(this.s); const yParityAndS = getBytes(this.s);
if (this.yParity) { yParityAndS[0] |= 0x80; } if (this.yParity) { yParityAndS[0] |= 0x80; }
return hexlify(yParityAndS); return hexlify(yParityAndS);
} }
@ -103,11 +107,11 @@ export class Signature implements Freezable<Signature> {
} }
constructor(guard: any, r: string, s: string, v: 27 | 28) { constructor(guard: any, r: string, s: string, v: 27 | 28) {
logger.assertPrivate(guard, _guard, "Signature"); assertPrivate(guard, _guard, "Signature");
this.#props = { r, s, v, networkV: null }; this.#props = { r, s, v, networkV: null };
} }
[Symbol.for('nodejs.util.inspect.custom')]() { [Symbol.for('nodejs.util.inspect.custom')](): string {
return `Signature { r: "${ this.r }", s: "${ this.s }", yParity: ${ this.yParity }, networkV: ${ this.networkV } }`; return `Signature { r: "${ this.r }", s: "${ this.s }", yParity: ${ this.yParity }, networkV: ${ this.networkV } }`;
} }
@ -141,25 +145,25 @@ export class Signature implements Freezable<Signature> {
// Get the chain ID from an EIP-155 v // Get the chain ID from an EIP-155 v
static getChainId(v: BigNumberish): bigint { static getChainId(v: BigNumberish): bigint {
const bv = logger.getBigInt(v, "v"); const bv = getBigInt(v, "v");
// The v is not an EIP-155 v, so it is the unspecified chain ID // The v is not an EIP-155 v, so it is the unspecified chain ID
if ((bv == BN_27) || (bv == BN_28)) { return BN_0; } if ((bv == BN_27) || (bv == BN_28)) { return BN_0; }
// Bad value for an EIP-155 v // Bad value for an EIP-155 v
if (bv < BN_35) { logger.throwArgumentError("invalid EIP-155 v", "v", v); } if (bv < BN_35) { throwArgumentError("invalid EIP-155 v", "v", v); }
return (bv - BN_35) / BN_2; return (bv - BN_35) / BN_2;
} }
// Get the EIP-155 v transformed for a given chainId // Get the EIP-155 v transformed for a given chainId
static getChainIdV(chainId: BigNumberish, v: 27 | 28): bigint { static getChainIdV(chainId: BigNumberish, v: 27 | 28): bigint {
return (logger.getBigInt(chainId) * BN_2) + BigInt(35 + v - 27); return (getBigInt(chainId) * BN_2) + BigInt(35 + v - 27);
} }
// Convert an EIP-155 v into a normalized v // Convert an EIP-155 v into a normalized v
static getNormalizedV(v: BigNumberish): 27 | 28 { static getNormalizedV(v: BigNumberish): 27 | 28 {
const bv = logger.getBigInt(v); const bv = getBigInt(v);
if (bv == BN_0) { return 27; } if (bv == BN_0) { return 27; }
if (bv == BN_1) { return 28; } if (bv == BN_1) { return 28; }
@ -170,11 +174,11 @@ export class Signature implements Freezable<Signature> {
static from(sig: SignatureLike): Signature { static from(sig: SignatureLike): Signature {
const throwError = (message: string) => { const throwError = (message: string) => {
return logger.throwArgumentError(message, "signature", sig); return throwArgumentError(message, "signature", sig);
}; };
if (typeof(sig) === "string") { if (typeof(sig) === "string") {
const bytes = logger.getBytes(sig, "signature"); const bytes = getBytes(sig, "signature");
if (bytes.length === 64) { if (bytes.length === 64) {
const r = hexlify(bytes.slice(0, 32)); const r = hexlify(bytes.slice(0, 32));
const s = bytes.slice(32, 64); const s = bytes.slice(32, 64);
@ -210,19 +214,19 @@ export class Signature implements Freezable<Signature> {
if (yParityAndS != null) { if (yParityAndS != null) {
if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); } if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); }
const bytes = logger.getBytes(yParityAndS); const bytes = getBytes(yParityAndS);
bytes[0] &= 0x7f; bytes[0] &= 0x7f;
return hexlify(bytes); return hexlify(bytes);
} }
return throwError("missing s"); return throwError("missing s");
})(sig.s, sig.yParityAndS); })(sig.s, sig.yParityAndS);
if (logger.getBytes(s)[0] & 0x80) { throwError("non-canonical s"); } if (getBytes(s)[0] & 0x80) { throwError("non-canonical s"); }
// Get v; by any means necessary (we check consistency below) // Get v; by any means necessary (we check consistency below)
const { networkV, v } = (function(_v?: BigNumberish, yParityAndS?: string, yParity?: number): { networkV?: bigint, v: 27 | 28 } { const { networkV, v } = (function(_v?: BigNumberish, yParityAndS?: string, yParity?: number): { networkV?: bigint, v: 27 | 28 } {
if (_v != null) { if (_v != null) {
const v = logger.getBigInt(_v); const v = getBigInt(_v);
return { return {
networkV: ((v >= BN_35) ? v: undefined), networkV: ((v >= BN_35) ? v: undefined),
v: Signature.getNormalizedV(v) v: Signature.getNormalizedV(v)
@ -231,7 +235,7 @@ export class Signature implements Freezable<Signature> {
if (yParityAndS != null) { if (yParityAndS != null) {
if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); } if (!isHexString(yParityAndS, 32)) { throwError("invalid yParityAndS"); }
return { v: ((logger.getBytes(yParityAndS)[0] & 0x80) ? 28: 27) }; return { v: ((getBytes(yParityAndS)[0] & 0x80) ? 28: 27) };
} }
if (yParity != null) { if (yParity != null) {

@ -1,7 +1,9 @@
import * as secp256k1 from "@noble/secp256k1"; import * as secp256k1 from "@noble/secp256k1";
import { concat, hexlify, toHex, logger } from "../utils/index.js"; import {
concat, getBytes, getBytesCopy, hexlify, toHex, throwArgumentError
} from "../utils/index.js";
import { computeHmac } from "./hmac.js"; import { computeHmac } from "./hmac.js";
import { Signature } from "./signature.js"; import { Signature } from "./signature.js";
@ -15,7 +17,7 @@ import type { SignatureLike } from "./index.js";
// Make noble-secp256k1 sync // Make noble-secp256k1 sync
secp256k1.utils.hmacSha256Sync = function(key: Uint8Array, ...messages: Array<Uint8Array>): Uint8Array { secp256k1.utils.hmacSha256Sync = function(key: Uint8Array, ...messages: Array<Uint8Array>): Uint8Array {
return logger.getBytes(computeHmac("sha256", key, concat(messages))); return getBytes(computeHmac("sha256", key, concat(messages)));
} }
export class SigningKey { export class SigningKey {
@ -40,7 +42,7 @@ export class SigningKey {
logger.assertArgument(() => (dataLength(digest) === 32), "invalid digest length", "digest", digest); logger.assertArgument(() => (dataLength(digest) === 32), "invalid digest length", "digest", digest);
*/ */
const [ sigDer, recid ] = secp256k1.signSync(logger.getBytesCopy(digest), logger.getBytesCopy(this.#privateKey), { const [ sigDer, recid ] = secp256k1.signSync(getBytesCopy(digest), getBytesCopy(this.#privateKey), {
recovered: true, recovered: true,
canonical: true canonical: true
}); });
@ -56,11 +58,11 @@ export class SigningKey {
computeShardSecret(other: BytesLike): string { computeShardSecret(other: BytesLike): string {
const pubKey = SigningKey.computePublicKey(other); const pubKey = SigningKey.computePublicKey(other);
return hexlify(secp256k1.getSharedSecret(logger.getBytesCopy(this.#privateKey), pubKey)); return hexlify(secp256k1.getSharedSecret(getBytesCopy(this.#privateKey), pubKey));
} }
static computePublicKey(key: BytesLike, compressed?: boolean): string { static computePublicKey(key: BytesLike, compressed?: boolean): string {
let bytes = logger.getBytes(key, "key"); let bytes = getBytes(key, "key");
if (bytes.length === 32) { if (bytes.length === 32) {
const pubKey = secp256k1.getPublicKey(bytes, !!compressed); const pubKey = secp256k1.getPublicKey(bytes, !!compressed);
@ -80,12 +82,12 @@ export class SigningKey {
static recoverPublicKey(digest: BytesLike, signature: SignatureLike): string { static recoverPublicKey(digest: BytesLike, signature: SignatureLike): string {
const sig = Signature.from(signature); const sig = Signature.from(signature);
const der = secp256k1.Signature.fromCompact(logger.getBytesCopy(concat([ sig.r, sig.s ]))).toDERRawBytes(); const der = secp256k1.Signature.fromCompact(getBytesCopy(concat([ sig.r, sig.s ]))).toDERRawBytes();
const pubKey = secp256k1.recoverPublicKey(logger.getBytesCopy(digest), der, sig.yParity); const pubKey = secp256k1.recoverPublicKey(getBytesCopy(digest), der, sig.yParity);
if (pubKey != null) { return hexlify(pubKey); } if (pubKey != null) { return hexlify(pubKey); }
return logger.throwArgumentError("invalid signautre for digest", "signature", signature); return throwArgumentError("invalid signautre for digest", "signature", signature);
} }
static _addPoints(p0: BytesLike, p1: BytesLike, compressed?: boolean): string { static _addPoints(p0: BytesLike, p1: BytesLike, compressed?: boolean): string {

@ -22,7 +22,7 @@ export {
export { export {
ZeroAddress, ZeroAddress,
NegativeOne, Zero, One, Two, WeiPerEther, MaxUint256, MinInt256, MaxInt256, NegativeOne, Zero, One, Two, WeiPerEther, MaxUint256, MinInt256, MaxInt256, N,
ZeroHash, ZeroHash,
EtherSymbol, MessagePrefix EtherSymbol, MessagePrefix
} from "./constants/index.js"; } from "./constants/index.js";
@ -47,7 +47,7 @@ export {
export { export {
id, id,
//isValidName, namehash, dnsEncode isValidName, namehash, dnsEncode,
hashMessage, hashMessage,
solidityPacked, solidityPackedKeccak256, solidityPackedSha256, solidityPacked, solidityPackedKeccak256, solidityPackedSha256,
TypedDataEncoder TypedDataEncoder
@ -65,7 +65,7 @@ export {
isCallException, isError, isCallException, isError,
FetchRequest, FetchResponse, FetchRequest, FetchResponse,
FixedFormat, FixedNumber, formatFixed, parseFixed, FixedFormat, FixedNumber, formatFixed, parseFixed,
assertArgument, Logger, logger, assertArgument,
fromTwos, toTwos, mask, toArray, toBigInt, toHex, toNumber, fromTwos, toTwos, mask, toArray, toBigInt, toHex, toNumber,
formatEther, parseEther, formatUnits, parseUnits, formatEther, parseEther, formatUnits, parseUnits,
_toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String, _toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String,

@ -1,6 +1,8 @@
import { keccak256 } from "../crypto/keccak.js"; import { keccak256 } from "../crypto/keccak.js";
import { concat, hexlify, logger, toUtf8Bytes, toUtf8String } from "../utils/index.js"; import {
concat, hexlify, throwArgumentError, toUtf8Bytes, toUtf8String
} from "../utils/index.js";
//import { ens_normalize } from "./ens-normalize/lib"; //import { ens_normalize } from "./ens-normalize/lib";
@ -55,7 +57,7 @@ export function isValidName(name: string): boolean {
export function namehash(name: string): string { export function namehash(name: string): string {
/* istanbul ignore if */ /* istanbul ignore if */
if (typeof(name) !== "string") { if (typeof(name) !== "string") {
logger.throwArgumentError("invalid ENS name; not a string", "name", name); throwArgumentError("invalid ENS name; not a string", "name", name);
} }
let result: string | Uint8Array = Zeros; let result: string | Uint8Array = Zeros;

@ -1,8 +1,10 @@
import { import {
concat, dataLength, hexlify, logger, toArray, toTwos, toUtf8Bytes, zeroPadBytes, zeroPadValue keccak256 as _keccak256, sha256 as _sha256
} from "../crypto/index.js";
import {
concat, dataLength, getBytes, hexlify, toArray, toTwos, toUtf8Bytes, zeroPadBytes, zeroPadValue,
throwArgumentError
} from "../utils/index.js"; } from "../utils/index.js";
import { keccak256 as _keccak256 } from "../crypto/keccak.js";
import { sha256 as _sha256 } from "../crypto/sha2.js";
const regexBytes = new RegExp("^bytes([0-9]+)$"); const regexBytes = new RegExp("^bytes([0-9]+)$");
@ -13,16 +15,16 @@ const regexArray = new RegExp("^(.*)\\[([0-9]*)\\]$");
function _pack(type: string, value: any, isArray?: boolean): Uint8Array { function _pack(type: string, value: any, isArray?: boolean): Uint8Array {
switch(type) { switch(type) {
case "address": case "address":
if (isArray) { return logger.getBytes(zeroPadValue(value, 32)); } if (isArray) { return getBytes(zeroPadValue(value, 32)); }
return logger.getBytes(value); return getBytes(value);
case "string": case "string":
return toUtf8Bytes(value); return toUtf8Bytes(value);
case "bytes": case "bytes":
return logger.getBytes(value); return getBytes(value);
case "bool": case "bool":
value = (!!value ? "0x01": "0x00"); value = (!!value ? "0x01": "0x00");
if (isArray) { return logger.getBytes(zeroPadValue(value, 32)); } if (isArray) { return getBytes(zeroPadValue(value, 32)); }
return logger.getBytes(value); return getBytes(value);
} }
let match = type.match(regexNumber); let match = type.match(regexNumber);
@ -30,14 +32,14 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array {
let size = parseInt(match[2] || "256") let size = parseInt(match[2] || "256")
if ((match[2] && String(size) !== match[2]) || (size % 8 !== 0) || size === 0 || size > 256) { if ((match[2] && String(size) !== match[2]) || (size % 8 !== 0) || size === 0 || size > 256) {
return logger.throwArgumentError("invalid number type", "type", type) return throwArgumentError("invalid number type", "type", type)
} }
if (isArray) { size = 256; } if (isArray) { size = 256; }
value = toTwos(value, size); value = toTwos(value, size);
return logger.getBytes(zeroPadValue(toArray(value), size / 8)); return getBytes(zeroPadValue(toArray(value), size / 8));
} }
match = type.match(regexBytes); match = type.match(regexBytes);
@ -45,12 +47,12 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array {
const size = parseInt(match[1]); const size = parseInt(match[1]);
if (String(size) !== match[1] || size === 0 || size > 32) { if (String(size) !== match[1] || size === 0 || size > 32) {
return logger.throwArgumentError("invalid bytes type", "type", type) return throwArgumentError("invalid bytes type", "type", type)
} }
if (dataLength(value) !== size) { if (dataLength(value) !== size) {
return logger.throwArgumentError(`invalid value for ${ type }`, "value", value) return throwArgumentError(`invalid value for ${ type }`, "value", value)
} }
if (isArray) { return logger.getBytes(zeroPadBytes(value, 32)); } if (isArray) { return getBytes(zeroPadBytes(value, 32)); }
return value; return value;
} }
@ -59,23 +61,23 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array {
const baseType = match[1]; const baseType = match[1];
const count = parseInt(match[2] || String(value.length)); const count = parseInt(match[2] || String(value.length));
if (count != value.length) { if (count != value.length) {
logger.throwArgumentError(`invalid array length for ${ type }`, "value", value) throwArgumentError(`invalid array length for ${ type }`, "value", value)
} }
const result: Array<Uint8Array> = []; const result: Array<Uint8Array> = [];
value.forEach(function(value) { value.forEach(function(value) {
result.push(_pack(baseType, value, true)); result.push(_pack(baseType, value, true));
}); });
return logger.getBytes(concat(result)); return getBytes(concat(result));
} }
return logger.throwArgumentError("invalid type", "type", type) return throwArgumentError("invalid type", "type", type)
} }
// @TODO: Array Enum // @TODO: Array Enum
export function solidityPacked(types: ReadonlyArray<string>, values: ReadonlyArray<any>) { export function solidityPacked(types: ReadonlyArray<string>, values: ReadonlyArray<any>): string {
if (types.length != values.length) { if (types.length != values.length) {
logger.throwArgumentError("wrong number of values; expected ${ types.length }", "values", values) throwArgumentError("wrong number of values; expected ${ types.length }", "values", values)
} }
const tight: Array<Uint8Array> = []; const tight: Array<Uint8Array> = [];
types.forEach(function(type, index) { types.forEach(function(type, index) {
@ -98,7 +100,7 @@ export function solidityPacked(types: ReadonlyArray<string>, values: ReadonlyArr
* *
* @see https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#non-standard-packed-mode * @see https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#non-standard-packed-mode
*/ */
export function solidityPackedKeccak256(types: ReadonlyArray<string>, values: ReadonlyArray<any>) { export function solidityPackedKeccak256(types: ReadonlyArray<string>, values: ReadonlyArray<any>): string {
return _keccak256(solidityPacked(types, values)); return _keccak256(solidityPacked(types, values));
} }
@ -110,6 +112,6 @@ export function solidityPackedKeccak256(types: ReadonlyArray<string>, values: Re
export function test(foo: number | string): void { export function test(foo: number | string): void {
} }
export function solidityPackedSha256(types: ReadonlyArray<string>, values: ReadonlyArray<any>) { export function solidityPackedSha256(types: ReadonlyArray<string>, values: ReadonlyArray<any>): string {
return _sha256(solidityPacked(types, values)); return _sha256(solidityPacked(types, values));
} }

@ -2,11 +2,11 @@
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { keccak256 } from "../crypto/index.js"; import { keccak256 } from "../crypto/index.js";
import { import {
concat, defineProperties, hexlify, isHexString, mask, toHex, toTwos, zeroPadValue concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toHex, toTwos, zeroPadValue,
throwArgumentError
} from "../utils/index.js"; } from "../utils/index.js";
import { id } from "./id.js"; import { id } from "./id.js";
import { logger } from "../utils/logger.js";
import type { BigNumberish, BytesLike } from "../utils/index.js"; import type { BigNumberish, BytesLike } from "../utils/index.js";
@ -32,8 +32,8 @@ export interface TypedDataField {
type: string; type: string;
}; };
function hexPadRight(value: BytesLike) { function hexPadRight(value: BytesLike): string {
const bytes = logger.getBytes(value); const bytes = getBytes(value);
const padOffset = bytes.length % 32 const padOffset = bytes.length % 32
if (padOffset) { if (padOffset) {
return concat([ bytes, padding.slice(padOffset) ]); return concat([ bytes, padding.slice(padOffset) ]);
@ -59,7 +59,7 @@ const domainFieldNames: Array<string> = [
function checkString(key: string): (value: any) => string { function checkString(key: string): (value: any) => string {
return function (value: any){ return function (value: any){
if (typeof(value) !== "string") { if (typeof(value) !== "string") {
logger.throwArgumentError(`invalid domain value for ${ JSON.stringify(key) }`, `domain.${ key }`, value); throwArgumentError(`invalid domain value for ${ JSON.stringify(key) }`, `domain.${ key }`, value);
} }
return value; return value;
} }
@ -69,18 +69,18 @@ const domainChecks: Record<string, (value: any) => any> = {
name: checkString("name"), name: checkString("name"),
version: checkString("version"), version: checkString("version"),
chainId: function(value: any) { chainId: function(value: any) {
return logger.getBigInt(value, "domain.chainId"); return getBigInt(value, "domain.chainId");
}, },
verifyingContract: function(value: any) { verifyingContract: function(value: any) {
try { try {
return getAddress(value).toLowerCase(); return getAddress(value).toLowerCase();
} catch (error) { } } catch (error) { }
return logger.throwArgumentError(`invalid domain value "verifyingContract"`, "domain.verifyingContract", value); return throwArgumentError(`invalid domain value "verifyingContract"`, "domain.verifyingContract", value);
}, },
salt: function(value: any) { salt: function(value: any) {
const bytes = logger.getBytes(value, "domain.salt"); const bytes = getBytes(value, "domain.salt");
if (bytes.length !== 32) { if (bytes.length !== 32) {
logger.throwArgumentError(`invalid domain value "salt"`, "domain.salt", value); throwArgumentError(`invalid domain value "salt"`, "domain.salt", value);
} }
return hexlify(bytes); return hexlify(bytes);
} }
@ -95,17 +95,17 @@ function getBaseEncoder(type: string): null | ((value: any) => string) {
const width = parseInt(match[2] || "256"); const width = parseInt(match[2] || "256");
if (width % 8 !== 0 || width > 256 || (match[2] && match[2] !== String(width))) { if (width % 8 !== 0 || width > 256 || (match[2] && match[2] !== String(width))) {
logger.throwArgumentError("invalid numeric width", "type", type); throwArgumentError("invalid numeric width", "type", type);
} }
const boundsUpper = mask(BN_MAX_UINT256, signed ? (width - 1): width); const boundsUpper = mask(BN_MAX_UINT256, signed ? (width - 1): width);
const boundsLower = signed ? ((boundsUpper + BN_1) * BN__1): BN_0; const boundsLower = signed ? ((boundsUpper + BN_1) * BN__1): BN_0;
return function(_value: BigNumberish) { return function(_value: BigNumberish) {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
if (value < boundsLower || value > boundsUpper) { if (value < boundsLower || value > boundsUpper) {
logger.throwArgumentError(`value out-of-bounds for ${ type }`, "value", value); throwArgumentError(`value out-of-bounds for ${ type }`, "value", value);
} }
return toHex(toTwos(value, 256), 32); return toHex(toTwos(value, 256), 32);
@ -119,13 +119,13 @@ function getBaseEncoder(type: string): null | ((value: any) => string) {
if (match) { if (match) {
const width = parseInt(match[1]); const width = parseInt(match[1]);
if (width === 0 || width > 32 || match[1] !== String(width)) { if (width === 0 || width > 32 || match[1] !== String(width)) {
logger.throwArgumentError("invalid bytes width", "type", type); throwArgumentError("invalid bytes width", "type", type);
} }
return function(value: BytesLike) { return function(value: BytesLike) {
const bytes = logger.getBytes(value); const bytes = getBytes(value);
if (bytes.length !== width) { if (bytes.length !== width) {
logger.throwArgumentError(`invalid length for ${ type }`, "value", value); throwArgumentError(`invalid length for ${ type }`, "value", value);
} }
return hexPadRight(value); return hexPadRight(value);
}; };
@ -193,14 +193,14 @@ export class TypedDataEncoder {
// Check each field has a unique name // Check each field has a unique name
if (uniqueNames.has(field.name)) { if (uniqueNames.has(field.name)) {
logger.throwArgumentError(`duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", types); throwArgumentError(`duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", types);
} }
uniqueNames.add(field.name); uniqueNames.add(field.name);
// Get the base type (drop any array specifiers) // Get the base type (drop any array specifiers)
const baseType = (<any>(field.type.match(/^([^\x5b]*)(\x5b|$)/)))[1] || null; const baseType = (<any>(field.type.match(/^([^\x5b]*)(\x5b|$)/)))[1] || null;
if (baseType === name) { if (baseType === name) {
logger.throwArgumentError(`circular type reference to ${ JSON.stringify(baseType) }`, "types", types); throwArgumentError(`circular type reference to ${ JSON.stringify(baseType) }`, "types", types);
} }
// Is this a base encoding type? // Is this a base encoding type?
@ -208,7 +208,7 @@ export class TypedDataEncoder {
if (encoder) { continue; } if (encoder) { continue; }
if (!parents.has(baseType)) { if (!parents.has(baseType)) {
logger.throwArgumentError(`unknown type ${ JSON.stringify(baseType) }`, "types", types); throwArgumentError(`unknown type ${ JSON.stringify(baseType) }`, "types", types);
} }
// Add linkage // Add linkage
@ -221,9 +221,9 @@ export class TypedDataEncoder {
const primaryTypes = Array.from(parents.keys()).filter((n) => ((parents.get(n) as Array<string>).length === 0)); const primaryTypes = Array.from(parents.keys()).filter((n) => ((parents.get(n) as Array<string>).length === 0));
if (primaryTypes.length === 0) { if (primaryTypes.length === 0) {
logger.throwArgumentError("missing primary type", "types", types); throwArgumentError("missing primary type", "types", types);
} else if (primaryTypes.length > 1) { } else if (primaryTypes.length > 1) {
logger.throwArgumentError(`ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", types); throwArgumentError(`ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", types);
} }
defineProperties<TypedDataEncoder>(this, { primaryType: primaryTypes[0] }); defineProperties<TypedDataEncoder>(this, { primaryType: primaryTypes[0] });
@ -231,7 +231,7 @@ export class TypedDataEncoder {
// Check for circular type references // Check for circular type references
function checkCircular(type: string, found: Set<string>) { function checkCircular(type: string, found: Set<string>) {
if (found.has(type)) { if (found.has(type)) {
logger.throwArgumentError(`circular type reference to ${ JSON.stringify(type) }`, "types", types); throwArgumentError(`circular type reference to ${ JSON.stringify(type) }`, "types", types);
} }
found.add(type); found.add(type);
@ -285,7 +285,7 @@ export class TypedDataEncoder {
const length = parseInt(match[3]); const length = parseInt(match[3]);
return (value: Array<any>) => { return (value: Array<any>) => {
if (length >= 0 && value.length !== length) { if (length >= 0 && value.length !== length) {
logger.throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value); throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value);
} }
let result = value.map(subEncoder); let result = value.map(subEncoder);
@ -312,13 +312,13 @@ export class TypedDataEncoder {
} }
} }
return logger.throwArgumentError(`unknown type: ${ type }`, "type", type); return throwArgumentError(`unknown type: ${ type }`, "type", type);
} }
encodeType(name: string): string { encodeType(name: string): string {
const result = this.#fullTypes.get(name); const result = this.#fullTypes.get(name);
if (!result) { if (!result) {
return logger.throwArgumentError(`unknown type: ${ JSON.stringify(name) }`, "name", name); return throwArgumentError(`unknown type: ${ JSON.stringify(name) }`, "name", name);
} }
return result; return result;
} }
@ -352,7 +352,7 @@ export class TypedDataEncoder {
const subtype = match[1]; const subtype = match[1];
const length = parseInt(match[3]); const length = parseInt(match[3]);
if (length >= 0 && value.length !== length) { if (length >= 0 && value.length !== length) {
logger.throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value); throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value);
} }
return value.map((v: any) => this._visit(subtype, v, callback)); return value.map((v: any) => this._visit(subtype, v, callback));
} }
@ -366,7 +366,7 @@ export class TypedDataEncoder {
}, <Record<string, any>>{}); }, <Record<string, any>>{});
} }
return logger.throwArgumentError(`unknown type: ${ type }`, "type", type); return throwArgumentError(`unknown type: ${ type }`, "type", type);
} }
visit(value: Record<string, any>, callback: (type: string, data: any) => any): any { visit(value: Record<string, any>, callback: (type: string, data: any) => any): any {
@ -390,7 +390,7 @@ export class TypedDataEncoder {
for (const name in domain) { for (const name in domain) {
const type = domainFieldTypes[name]; const type = domainFieldTypes[name];
if (!type) { if (!type) {
logger.throwArgumentError(`invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain); throwArgumentError(`invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain);
} }
domainFields.push({ name, type }); domainFields.push({ name, type });
} }
@ -476,7 +476,7 @@ export class TypedDataEncoder {
const typesWithDomain = Object.assign({ }, types); const typesWithDomain = Object.assign({ }, types);
if (typesWithDomain.EIP712Domain) { if (typesWithDomain.EIP712Domain) {
logger.throwArgumentError("types must not contain EIP712Domain type", "types.EIP712Domain", types); throwArgumentError("types must not contain EIP712Domain type", "types.EIP712Domain", types);
} else { } else {
typesWithDomain.EIP712Domain = domainTypes; typesWithDomain.EIP712Domain = domainTypes;
} }
@ -492,12 +492,12 @@ export class TypedDataEncoder {
// bytes // bytes
if (type.match(/^bytes(\d*)/)) { if (type.match(/^bytes(\d*)/)) {
return hexlify(logger.getBytes(value)); return hexlify(getBytes(value));
} }
// uint or int // uint or int
if (type.match(/^u?int/)) { if (type.match(/^u?int/)) {
return logger.getBigInt(value).toString(); return getBigInt(value).toString();
} }
switch (type) { switch (type) {
@ -507,12 +507,12 @@ export class TypedDataEncoder {
return !!value; return !!value;
case "string": case "string":
if (typeof(value) !== "string") { if (typeof(value) !== "string") {
logger.throwArgumentError(`invalid string`, "value", value); throwArgumentError(`invalid string`, "value", value);
} }
return value; return value;
} }
return logger.throwArgumentError("unsupported type", "type", type); return throwArgumentError("unsupported type", "type", type);
}) })
}; };
} }

@ -7,14 +7,15 @@
// of Signer/ENS name to address so we can sync respond to listenerCount. // of Signer/ENS name to address so we can sync respond to listenerCount.
import { resolveAddress } from "../address/index.js"; import { resolveAddress } from "../address/index.js";
import { concat, dataLength, dataSlice, hexlify, isHexString } from "../utils/data.js"; import {
import { isCallException } from "../utils/errors.js"; concat, dataLength, dataSlice, hexlify, isHexString,
import { FetchRequest } from "../utils/fetch.js"; getBigInt, getBytes, getNumber,
import { toArray, toQuantity } from "../utils/maths.js"; isCallException, makeError, throwError, throwArgumentError,
import { defineProperties, EventPayload, resolveProperties } from "../utils/index.js"; FetchRequest,
import { toUtf8String } from "../utils/index.js";; toArray, toQuantity,
defineProperties, EventPayload, resolveProperties,
import { logger } from "../utils/logger.js"; toUtf8String
} from "../utils/index.js";
import { EnsResolver } from "./ens-resolver.js"; import { EnsResolver } from "./ens-resolver.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
@ -193,7 +194,7 @@ async function getSubscription(_event: ProviderEvent, provider: AbstractProvider
return { filter, tag: getTag("event", filter), type: "event" }; return { filter, tag: getTag("event", filter), type: "event" };
} }
return logger.throwArgumentError("unknown ProviderEvent", "event", _event); return throwArgumentError("unknown ProviderEvent", "event", _event);
} }
function getTime(): number { return (new Date()).getTime(); } function getTime(): number { return (new Date()).getTime(); }
@ -428,7 +429,7 @@ export class AbstractProvider implements Provider {
// 4xx indicates the result is not present; stop // 4xx indicates the result is not present; stop
if (resp.statusCode >= 400 && resp.statusCode < 500) { if (resp.statusCode >= 400 && resp.statusCode < 500) {
return logger.throwError(`response not found during CCIP fetch: ${ errorMessage }`, "OFFCHAIN_FAULT", { return throwError(`response not found during CCIP fetch: ${ errorMessage }`, "OFFCHAIN_FAULT", {
reason: "404_MISSING_RESOURCE", reason: "404_MISSING_RESOURCE",
transaction: tx, info: { url, errorMessage } transaction: tx, info: { url, errorMessage }
}); });
@ -438,7 +439,7 @@ export class AbstractProvider implements Provider {
errorMessages.push(errorMessage); errorMessages.push(errorMessage);
} }
return logger.throwError(`error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, "OFFCHAIN_FAULT", { return throwError(`error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, "OFFCHAIN_FAULT", {
reason: "500_SERVER_ERROR", reason: "500_SERVER_ERROR",
transaction: tx, info: { urls, errorMessages } transaction: tx, info: { urls, errorMessages }
}); });
@ -449,7 +450,7 @@ export class AbstractProvider implements Provider {
} }
_detectNetwork(): Promise<Frozen<Network>> { _detectNetwork(): Promise<Frozen<Network>> {
return logger.throwError("sub-classes must implement this", "UNSUPPORTED_OPERATION", { return throwError("sub-classes must implement this", "UNSUPPORTED_OPERATION", {
operation: "_detectNetwork" operation: "_detectNetwork"
}); });
} }
@ -457,7 +458,7 @@ export class AbstractProvider implements Provider {
// Sub-classes should override this and handle PerformActionRequest requests, calling // Sub-classes should override this and handle PerformActionRequest requests, calling
// the super for any unhandled actions. // the super for any unhandled actions.
async _perform<T = any>(req: PerformActionRequest): Promise<T> { async _perform<T = any>(req: PerformActionRequest): Promise<T> {
return logger.throwError(`unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", { return throwError(`unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", {
operation: req.method, operation: req.method,
info: req info: req
}); });
@ -465,7 +466,7 @@ export class AbstractProvider implements Provider {
// State // State
async getBlockNumber(): Promise<number> { async getBlockNumber(): Promise<number> {
return logger.getNumber(await this.#perform({ method: "getBlockNumber" }), "%response"); return getNumber(await this.#perform({ method: "getBlockNumber" }), "%response");
} }
// @TODO: Make this string | Promsie<string> so no await needed if sync is possible // @TODO: Make this string | Promsie<string> so no await needed if sync is possible
@ -502,7 +503,7 @@ export class AbstractProvider implements Provider {
return this.getBlockNumber().then((b) => toQuantity(b + blockTag)); return this.getBlockNumber().then((b) => toQuantity(b + blockTag));
} }
return logger.throwArgumentError("invalid blockTag", "blockTag", blockTag); return throwArgumentError("invalid blockTag", "blockTag", blockTag);
} }
async getNetwork(): Promise<Frozen<Network>> { async getNetwork(): Promise<Frozen<Network>> {
@ -544,7 +545,7 @@ export class AbstractProvider implements Provider {
} }
} else { } else {
// Otherwise, we do not allow changes to the underlying network // Otherwise, we do not allow changes to the underlying network
logger.throwError(`network changed: ${ expected.chainId } => ${ actual.chainId } `, "NETWORK_ERROR", { throwError(`network changed: ${ expected.chainId } => ${ actual.chainId } `, "NETWORK_ERROR", {
event: "changed" event: "changed"
}); });
} }
@ -559,7 +560,7 @@ export class AbstractProvider implements Provider {
gasPrice: ((async () => { gasPrice: ((async () => {
try { try {
const gasPrice = await this.#perform({ method: "getGasPrice" }); const gasPrice = await this.#perform({ method: "getGasPrice" });
return logger.getBigInt(gasPrice, "%response"); return getBigInt(gasPrice, "%response");
} catch (error) { } } catch (error) { }
return null return null
})()) })())
@ -598,14 +599,14 @@ export class AbstractProvider implements Provider {
async estimateGas(_tx: TransactionRequest) { async estimateGas(_tx: TransactionRequest) {
const transaction = await this._getTransaction(_tx); const transaction = await this._getTransaction(_tx);
return logger.getBigInt(await this.#perform({ return getBigInt(await this.#perform({
method: "estimateGas", transaction method: "estimateGas", transaction
}), "%response"); }), "%response");
} }
async #call(tx: PerformActionTransaction, blockTag: string, attempt: number): Promise<string> { async #call(tx: PerformActionTransaction, blockTag: string, attempt: number): Promise<string> {
if (attempt >= MAX_CCIP_REDIRECTS) { if (attempt >= MAX_CCIP_REDIRECTS) {
logger.throwError("CCIP read exceeded maximum redirections", "OFFCHAIN_FAULT", { throwError("CCIP read exceeded maximum redirections", "OFFCHAIN_FAULT", {
reason: "TOO_MANY_REDIRECTS", reason: "TOO_MANY_REDIRECTS",
transaction: Object.assign({ }, tx, { blockTag, enableCcipRead: true }) transaction: Object.assign({ }, tx, { blockTag, enableCcipRead: true })
}); });
@ -628,7 +629,7 @@ export class AbstractProvider implements Provider {
try { try {
ccipArgs = parseOffchainLookup(dataSlice(error.data, 4)); ccipArgs = parseOffchainLookup(dataSlice(error.data, 4));
} catch (error: any) { } catch (error: any) {
return logger.throwError(error.message, "OFFCHAIN_FAULT", { return throwError(error.message, "OFFCHAIN_FAULT", {
reason: "BAD_DATA", reason: "BAD_DATA",
transaction, info: { data } transaction, info: { data }
}); });
@ -636,7 +637,7 @@ export class AbstractProvider implements Provider {
// Check the sender of the OffchainLookup matches the transaction // Check the sender of the OffchainLookup matches the transaction
if (ccipArgs.sender.toLowerCase() !== txSender.toLowerCase()) { if (ccipArgs.sender.toLowerCase() !== txSender.toLowerCase()) {
return logger.throwError("CCIP Read sender mismatch", "CALL_EXCEPTION", { return throwError("CCIP Read sender mismatch", "CALL_EXCEPTION", {
data, transaction, data, transaction,
errorSignature: "OffchainLookup(address,string[],bytes,bytes4,bytes)", errorSignature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
errorName: "OffchainLookup", errorName: "OffchainLookup",
@ -646,7 +647,7 @@ export class AbstractProvider implements Provider {
const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls); const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls);
if (ccipResult == null) { if (ccipResult == null) {
return logger.throwError("CCIP Read failed to fetch data", "OFFCHAIN_FAULT", { return throwError("CCIP Read failed to fetch data", "OFFCHAIN_FAULT", {
reason: "FETCH_FAILED", reason: "FETCH_FAILED",
transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs } transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs }
}); });
@ -684,11 +685,11 @@ export class AbstractProvider implements Provider {
} }
async getBalance(address: AddressLike, blockTag?: BlockTag) { async getBalance(address: AddressLike, blockTag?: BlockTag) {
return logger.getBigInt(await this.#getAccountValue({ method: "getBalance" }, address, blockTag), "%response"); return getBigInt(await this.#getAccountValue({ method: "getBalance" }, address, blockTag), "%response");
} }
async getTransactionCount(address: AddressLike, blockTag?: BlockTag) { async getTransactionCount(address: AddressLike, blockTag?: BlockTag) {
return logger.getNumber(await this.#getAccountValue({ method: "getTransactionCount" }, address, blockTag), "%response"); return getNumber(await this.#getAccountValue({ method: "getTransactionCount" }, address, blockTag), "%response");
} }
async getCode(address: AddressLike, blockTag?: BlockTag) { async getCode(address: AddressLike, blockTag?: BlockTag) {
@ -696,7 +697,7 @@ export class AbstractProvider implements Provider {
} }
async getStorageAt(address: AddressLike, _position: BigNumberish, blockTag?: BlockTag) { async getStorageAt(address: AddressLike, _position: BigNumberish, blockTag?: BlockTag) {
const position = logger.getBigInt(_position, "position"); const position = getBigInt(_position, "position");
return hexlify(await this.#getAccountValue({ method: "getStorageAt", position }, address, blockTag)); return hexlify(await this.#getAccountValue({ method: "getStorageAt", position }, address, blockTag));
} }
@ -852,7 +853,7 @@ export class AbstractProvider implements Provider {
// ENS // ENS
_getProvider(chainId: number): AbstractProvider { _getProvider(chainId: number): AbstractProvider {
return logger.throwError("provider cannot connect to target network", "UNSUPPORTED_OPERATION", { return throwError("provider cannot connect to target network", "UNSUPPORTED_OPERATION", {
operation: "_getProvider()" operation: "_getProvider()"
}); });
} }
@ -919,7 +920,7 @@ export class AbstractProvider implements Provider {
if (timer == null) { return; } if (timer == null) { return; }
timer = null; timer = null;
this.off("block", listener); this.off("block", listener);
reject(logger.makeError("timeout", "TIMEOUT", { reason: "timeout" })); reject(makeError("timeout", "TIMEOUT", { reason: "timeout" }));
}, timeout); }, timeout);
} }
@ -1153,7 +1154,7 @@ export class AbstractProvider implements Provider {
pause(dropWhilePaused?: boolean): void { pause(dropWhilePaused?: boolean): void {
if (this.#pausedState != null) { if (this.#pausedState != null) {
if (this.#pausedState == !!dropWhilePaused) { return; } if (this.#pausedState == !!dropWhilePaused) { return; }
return logger.throwError("cannot change pause type; resume first", "UNSUPPORTED_OPERATION", { return throwError("cannot change pause type; resume first", "UNSUPPORTED_OPERATION", {
operation: "pause" operation: "pause"
}); });
} }
@ -1201,8 +1202,8 @@ function _parseString(result: string, start: number): null | string {
function _parseBytes(result: string, start: number): null | string { function _parseBytes(result: string, start: number): null | string {
if (result === "0x") { return null; } if (result === "0x") { return null; }
try { try {
const offset = logger.getNumber(dataSlice(result, start, start + 32)); const offset = getNumber(dataSlice(result, start, start + 32));
const length = logger.getNumber(dataSlice(result, offset, offset + 32)); const length = getNumber(dataSlice(result, offset, offset + 32));
return dataSlice(result, offset + 32, offset + 32 + length); return dataSlice(result, offset + 32, offset + 32 + length);
} catch (error) { } } catch (error) { }
@ -1241,7 +1242,7 @@ function encodeBytes(datas: Array<BytesLike>) {
} }
for (let i = 0; i < datas.length; i++) { for (let i = 0; i < datas.length; i++) {
const data = logger.getBytes(datas[i]); const data = getBytes(datas[i]);
// Update the bytes offset // Update the bytes offset
result[i] = numPad(byteCount); result[i] = numPad(byteCount);
@ -1274,8 +1275,8 @@ function parseOffchainLookup(data: string): CcipArgs {
// Read the URLs from the response // Read the URLs from the response
try { try {
const urls: Array<string> = []; const urls: Array<string> = [];
const urlsOffset = logger.getNumber(dataSlice(data, 32, 64)); const urlsOffset = getNumber(dataSlice(data, 32, 64));
const urlsLength = logger.getNumber(dataSlice(data, urlsOffset, urlsOffset + 32)); const urlsLength = getNumber(dataSlice(data, urlsOffset, urlsOffset + 32));
const urlsData = dataSlice(data, urlsOffset + 32); const urlsData = dataSlice(data, urlsOffset + 32);
for (let u = 0; u < urlsLength; u++) { for (let u = 0; u < urlsLength; u++) {
const url = _parseString(urlsData, u * 32); const url = _parseString(urlsData, u * 32);

@ -1,6 +1,8 @@
import { logger } from "../utils/logger.js";
import { Transaction } from "../transaction/index.js"; import { Transaction } from "../transaction/index.js";
import { defineProperties, resolveProperties } from "../utils/index.js"; import {
defineProperties, resolveProperties,
throwArgumentError, throwError
} from "../utils/index.js";
import type { TypedDataDomain, TypedDataField } from "../hash/index.js"; import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
import type { TransactionLike } from "../transaction/index.js"; import type { TransactionLike } from "../transaction/index.js";
@ -23,7 +25,7 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
#checkProvider(operation: string): Provider { #checkProvider(operation: string): Provider {
if (this.provider) { return this.provider; } if (this.provider) { return this.provider; }
return logger.throwError("missing provider", "UNSUPPORTED_OPERATION", { operation }); return throwError("missing provider", "UNSUPPORTED_OPERATION", { operation });
} }
async getNonce(blockTag?: BlockTag): Promise<number> { async getNonce(blockTag?: BlockTag): Promise<number> {
@ -39,7 +41,7 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
if (pop.to != null) { if (pop.to != null) {
pop.to = provider.resolveName(pop.to).then((to) => { pop.to = provider.resolveName(pop.to).then((to) => {
if (to == null) { if (to == null) {
return logger.throwArgumentError("transaction to ENS name not configured", "tx.to", pop.to); return throwArgumentError("transaction to ENS name not configured", "tx.to", pop.to);
} }
return to; return to;
}); });
@ -52,7 +54,7 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
this.resolveName(from) this.resolveName(from)
]).then(([ address, from ]) => { ]).then(([ address, from ]) => {
if (!from || address.toLowerCase() !== from.toLowerCase()) { if (!from || address.toLowerCase() !== from.toLowerCase()) {
return logger.throwArgumentError("transaction from mismatch", "tx.from", from); return throwArgumentError("transaction from mismatch", "tx.from", from);
} }
return address; return address;
}); });
@ -123,7 +125,7 @@ export class VoidSigner extends AbstractSigner {
} }
#throwUnsupported(suffix: string, operation: string): never { #throwUnsupported(suffix: string, operation: string): never {
return logger.throwError(`VoidSigner cannot sign ${ suffix }`, "UNSUPPORTED_OPERATION", { return throwError(`VoidSigner cannot sign ${ suffix }`, "UNSUPPORTED_OPERATION", {
operation operation
}); });
} }

@ -1,11 +1,12 @@
import { ZeroHash } from "../constants/hashes.js"; import { ZeroHash } from "../constants/hashes.js";
import { dnsEncode, namehash } from "../hash/index.js"; import { dnsEncode, namehash } from "../hash/index.js";
import { import {
defineProperties, encodeBase58, toArray, toNumber, toUtf8Bytes, toUtf8String concat, dataSlice, getBytes, hexlify, zeroPadValue,
defineProperties, encodeBase58, getBigInt, toArray,
toNumber, toUtf8Bytes, toUtf8String,
throwArgumentError, throwError,
FetchRequest
} from "../utils/index.js"; } from "../utils/index.js";
import { concat, dataSlice, hexlify, zeroPadValue } from "../utils/data.js";
import { FetchRequest } from "../utils/fetch.js";
import { logger } from "../utils/logger.js";
import type { BigNumberish, BytesLike, EthersError } from "../utils/index.js"; import type { BigNumberish, BytesLike, EthersError } from "../utils/index.js";
@ -64,7 +65,7 @@ function encodeBytes(datas: Array<BytesLike>) {
} }
for (let i = 0; i < datas.length; i++) { for (let i = 0; i < datas.length; i++) {
const data = logger.getBytes(datas[i]); const data = getBytes(datas[i]);
// Update the bytes offset // Update the bytes offset
result[i] = numPad(byteCount); result[i] = numPad(byteCount);
@ -86,7 +87,7 @@ function getIpfsLink(link: string): string {
} else if (link.match(/^ipfs:\/\//i)) { } else if (link.match(/^ipfs:\/\//i)) {
link = link.substring(7); link = link.substring(7);
} else { } else {
logger.throwArgumentError("unsupported IPFS format", "link", link); throwArgumentError("unsupported IPFS format", "link", link);
} }
return `https:/\/gateway.ipfs.io/ipfs/${ link }`; return `https:/\/gateway.ipfs.io/ipfs/${ link }`;
@ -170,7 +171,7 @@ export class EnsResolver {
to: this.address, to: this.address,
data: "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000" data: "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000"
}).then((result) => { }).then((result) => {
return (logger.getBigInt(result) === BN_1); return (getBigInt(result) === BN_1);
}).catch((error) => { }).catch((error) => {
if (error.code === "CALL_EXCEPTION") { return false; } if (error.code === "CALL_EXCEPTION") { return false; }
// Rethrow the error: link is down, etc. Let future attempts retry. // Rethrow the error: link is down, etc. Let future attempts retry.
@ -203,8 +204,8 @@ export class EnsResolver {
try { try {
let data = await this.provider.call(tx); let data = await this.provider.call(tx);
if ((logger.getBytes(data).length % 32) === 4) { if ((getBytes(data).length % 32) === 4) {
return logger.throwError("resolver threw error", "CALL_EXCEPTION", { return throwError("resolver threw error", "CALL_EXCEPTION", {
transaction: tx, data transaction: tx, data
}); });
} }
@ -256,7 +257,7 @@ export class EnsResolver {
if (address != null) { return address; } if (address != null) { return address; }
return logger.throwError(`invalid coin data`, "UNSUPPORTED_OPERATION", { return throwError(`invalid coin data`, "UNSUPPORTED_OPERATION", {
operation: `getAddress(${ coinType })`, operation: `getAddress(${ coinType })`,
info: { coinType, data } info: { coinType, data }
}); });
@ -268,7 +269,7 @@ export class EnsResolver {
// The nodehash consumes the first slot, so the string pointer targets // The nodehash consumes the first slot, so the string pointer targets
// offset 64, with the length at offset 64 and data starting at offset 96 // offset 64, with the length at offset 64 and data starting at offset 96
const calldata = logger.getBytes(concat([ numPad(64), numPad(keyBytes.length), keyBytes ])); const calldata = getBytes(concat([ numPad(64), numPad(keyBytes.length), keyBytes ]));
const hexBytes = parseBytes((await this._fetch("0x59d1d43c", bytesPad(calldata))) || "0x", 0); const hexBytes = parseBytes((await this._fetch("0x59d1d43c", bytesPad(calldata))) || "0x", 0);
if (hexBytes == null || hexBytes === "0x") { return null; } if (hexBytes == null || hexBytes === "0x") { return null; }
@ -299,7 +300,7 @@ export class EnsResolver {
return `bzz:/\/${ swarm[1] }`; return `bzz:/\/${ swarm[1] }`;
} }
return logger.throwError(`invalid or unsupported content hash data`, "UNSUPPORTED_OPERATION", { return throwError(`invalid or unsupported content hash data`, "UNSUPPORTED_OPERATION", {
operation: "getContentHash()", operation: "getContentHash()",
info: { data: hexBytes } info: { data: hexBytes }
}); });
@ -377,7 +378,7 @@ export class EnsResolver {
} else if (scheme === "erc1155") { } else if (scheme === "erc1155") {
// balanceOf(address owner, uint256 tokenId) // balanceOf(address owner, uint256 tokenId)
const balance = logger.getBigInt(await this.provider.call({ const balance = getBigInt(await this.provider.call({
to: addr, data: concat([ "0x00fdd58e", zeroPadValue(owner, 32), tokenId ]) to: addr, data: concat([ "0x00fdd58e", zeroPadValue(owner, 32), tokenId ])
})); }));
if (!balance) { if (!balance) {
@ -479,7 +480,7 @@ export class EnsResolver {
// No ENS... // No ENS...
if (!ensPlugin) { if (!ensPlugin) {
return logger.throwError("network does not support ENS", "UNSUPPORTED_OPERATION", { return throwError("network does not support ENS", "UNSUPPORTED_OPERATION", {
operation: "getResolver", info: { network: network.name } operation: "getResolver", info: { network: network.name }
}); });
} }

@ -16,9 +16,10 @@
*/ */
import { getAddress, getCreateAddress } from "../address/index.js"; import { getAddress, getCreateAddress } from "../address/index.js";
import { dataLength, dataSlice, isHexString } from "../utils/data.js"; import {
import { toQuantity } from "../utils/maths.js"; dataLength, dataSlice, getBigInt, getNumber, isHexString, toQuantity,
import { logger } from "../utils/logger.js"; throwArgumentError, throwError
} from "../utils/index.js";
import { Signature } from "../crypto/signature.js"; import { Signature } from "../crypto/signature.js";
import { accessListify } from "../transaction/index.js"; import { accessListify } from "../transaction/index.js";
@ -168,7 +169,7 @@ export class Formatter {
// An address from a call result; may be zero-padded // An address from a call result; may be zero-padded
callAddress(value: any): string { callAddress(value: any): string {
if (dataLength(value) !== 32 || dataSlice(value, 0, 12) !== "0x000000000000000000000000") { if (dataLength(value) !== 32 || dataSlice(value, 0, 12) !== "0x000000000000000000000000") {
logger.throwArgumentError("invalid call address", "value", value); throwArgumentError("invalid call address", "value", value);
} }
return this.address(dataSlice(value, 12)); return this.address(dataSlice(value, 12));
} }
@ -177,7 +178,7 @@ export class Formatter {
contractAddress(value: any): string { contractAddress(value: any): string {
return getCreateAddress({ return getCreateAddress({
from: this.address(value.from), from: this.address(value.from),
nonce: logger.getNumber(value.nonce, "value.nonce") nonce: getNumber(value.nonce, "value.nonce")
}); });
} }
@ -196,7 +197,7 @@ export class Formatter {
return toQuantity(value); return toQuantity(value);
} }
return logger.throwArgumentError("invalid blockTag", "value", value); return throwArgumentError("invalid blockTag", "value", value);
} }
// Block objects // Block objects
@ -279,20 +280,20 @@ export class Formatter {
if (value === 0 || value === 1) { if (value === 0 || value === 1) {
// Make sure if both are specified, they match // Make sure if both are specified, they match
if (receipt.status != null && receipt.status !== value) { if (receipt.status != null && receipt.status !== value) {
return logger.throwError("alt-root-status/status mismatch", "BAD_DATA", { return throwError("alt-root-status/status mismatch", "BAD_DATA", {
value: { root: receipt.root, status: receipt.status } value: { root: receipt.root, status: receipt.status }
}); });
} }
receipt.status = value; receipt.status = value;
delete receipt.root; delete receipt.root;
} else { } else {
return logger.throwError("invalid alt-root-status", "BAD_DATA", { return throwError("invalid alt-root-status", "BAD_DATA", {
value: receipt.root value: receipt.root
}); });
} }
} else if (!isHexString(receipt.root, 32)) { } else if (!isHexString(receipt.root, 32)) {
// Must be a valid bytes32 // Must be a valid bytes32
return logger.throwError("invalid receipt root hash", "BAD_DATA", { return throwError("invalid receipt root hash", "BAD_DATA", {
value: receipt.root value: receipt.root
}); });
} }
@ -356,13 +357,13 @@ export class Formatter {
// Requires a value which is a value BigNumber // Requires a value which is a value BigNumber
bigNumber(value: any): bigint { bigNumber(value: any): bigint {
return logger.getBigInt(value, "value"); return getBigInt(value, "value");
} }
uint256(value: any): bigint { uint256(value: any): bigint {
const result = this.bigNumber(value); const result = this.bigNumber(value);
if (result < 0 || result > BN_MAX_UINT256) { if (result < 0 || result > BN_MAX_UINT256) {
logger.throwArgumentError("invalid uint256", "value", value); throwArgumentError("invalid uint256", "value", value);
} }
return result; return result;
} }
@ -375,7 +376,7 @@ export class Formatter {
case false: case "false": case false: case "false":
return false; return false;
} }
return logger.throwArgumentError(`invalid boolean; ${ JSON.stringify(value) }`, "value", value); return throwArgumentError(`invalid boolean; ${ JSON.stringify(value) }`, "value", value);
} }
// Requires a value which is a valid hexstring. If dataOrLength is true, // Requires a value which is a valid hexstring. If dataOrLength is true,
@ -393,7 +394,7 @@ export class Formatter {
data(value: string): string { data(value: string): string {
if (dataLength(value) == null) { if (dataLength(value) == null) {
logger.throwArgumentError("", "value", value); throwArgumentError("", "value", value);
} }
return value; return value;
} }
@ -401,14 +402,14 @@ export class Formatter {
// Requires a network-native hash // Requires a network-native hash
hash(value: any): string { hash(value: any): string {
if (dataLength(value) !== 32) { if (dataLength(value) !== 32) {
logger.throwArgumentError("", "value", value); throwArgumentError("", "value", value);
} }
return this.#format.data(value); return this.#format.data(value);
} }
// Requires a valid number, within the IEEE 754 safe range // Requires a valid number, within the IEEE 754 safe range
number(value: any): number { number(value: any): number {
return logger.getNumber(value); return getNumber(value);
} }
// Requires an object which matches a fleet of other formatters // Requires an object which matches a fleet of other formatters
@ -433,7 +434,7 @@ export class Formatter {
if (nv !== undefined) { result[key] = nv; } if (nv !== undefined) { result[key] = nv; }
} catch (error) { } catch (error) {
const message = (error instanceof Error) ? error.message: "not-an-error"; const message = (error instanceof Error) ? error.message: "not-an-error";
logger.throwError(`invalid value for value.${ key } (${ message })`, "BAD_DATA", { value }) throwError(`invalid value for value.${ key } (${ message })`, "BAD_DATA", { value })
} }
} }
return result; return result;

@ -1,5 +1,6 @@
import { logger } from "../utils/logger.js"; import {
import { getStore, setStore } from "../utils/storage.js"; getStore, getBigInt, setStore, throwArgumentError
} from "../utils/index.js";
import { Formatter } from "./formatter.js"; import { Formatter } from "./formatter.js";
import { EnsPlugin, GasCostPlugin } from "./plugins-network.js"; import { EnsPlugin, GasCostPlugin } from "./plugins-network.js";
@ -96,7 +97,7 @@ export class Network implements Freezable<Network> {
}; };
constructor(name: string, _chainId: BigNumberish, formatter?: Formatter) { constructor(name: string, _chainId: BigNumberish, formatter?: Formatter) {
const chainId = logger.getBigInt(_chainId); const chainId = getBigInt(_chainId);
if (formatter == null) { formatter = defaultFormatter; } if (formatter == null) { formatter = defaultFormatter; }
const plugins = new Map(); const plugins = new Map();
this.#props = { name, chainId, formatter, plugins }; this.#props = { name, chainId, formatter, plugins };
@ -110,7 +111,7 @@ export class Network implements Freezable<Network> {
set name(value: string) { setStore(this.#props, "name", value); } set name(value: string) { setStore(this.#props, "name", value); }
get chainId(): bigint { return getStore(this.#props, "chainId"); } get chainId(): bigint { return getStore(this.#props, "chainId"); }
set chainId(value: BigNumberish) { setStore(this.#props, "chainId", logger.getBigInt(value, "chainId")); } set chainId(value: BigNumberish) { setStore(this.#props, "chainId", getBigInt(value, "chainId")); }
get formatter(): Formatter { return getStore(this.#props, "formatter"); } get formatter(): Formatter { return getStore(this.#props, "formatter"); }
set formatter(value: Formatter) { setStore(this.#props, "formatter", value); } set formatter(value: Formatter) { setStore(this.#props, "formatter", value); }
@ -192,7 +193,7 @@ export class Network implements Freezable<Network> {
return new Network("unknown", network); return new Network("unknown", network);
} }
logger.throwArgumentError("unknown network", "network", network); throwArgumentError("unknown network", "network", network);
} }
// Clonable with network-like abilities // Clonable with network-like abilities
@ -206,7 +207,7 @@ export class Network implements Freezable<Network> {
// Networkish // Networkish
if (typeof(network) === "object") { if (typeof(network) === "object") {
if (typeof(network.name) !== "string" || typeof(network.chainId) !== "number") { if (typeof(network.name) !== "string" || typeof(network.chainId) !== "number") {
logger.throwArgumentError("invalid network object name or chainId", "network", network); throwArgumentError("invalid network object name or chainId", "network", network);
} }
const custom = new Network(<string>(network.name), <number>(network.chainId)); const custom = new Network(<string>(network.name), <number>(network.chainId));
@ -222,14 +223,14 @@ export class Network implements Freezable<Network> {
return custom; return custom;
} }
return logger.throwArgumentError("invalid network", "network", network); return throwArgumentError("invalid network", "network", network);
} }
static register(nameOrChainId: string | number | bigint, networkFunc: () => Network): void { static register(nameOrChainId: string | number | bigint, networkFunc: () => Network): void {
if (typeof(nameOrChainId) === "number") { nameOrChainId = BigInt(nameOrChainId); } if (typeof(nameOrChainId) === "number") { nameOrChainId = BigInt(nameOrChainId); }
const existing = Networks.get(nameOrChainId); const existing = Networks.get(nameOrChainId);
if (existing) { if (existing) {
logger.throwArgumentError(`conflicting network for ${ JSON.stringify(existing.name) }`, "nameOrChainId", nameOrChainId); throwArgumentError(`conflicting network for ${ JSON.stringify(existing.name) }`, "nameOrChainId", nameOrChainId);
} }
Networks.set(nameOrChainId, networkFunc); Networks.set(nameOrChainId, networkFunc);
} }

@ -1,6 +1,6 @@
import { defineProperties } from "../utils/properties.js"; import { defineProperties } from "../utils/properties.js";
import { logger } from "../utils/logger.js"; import { throwArgumentError } from "../utils/index.js";
//import { BigNumberish } from "../math/index.js"; //import { BigNumberish } from "../math/index.js";
@ -56,7 +56,7 @@ export class GasCostPlugin extends NetworkPlugin {
let value = (costs || { })[name]; let value = (costs || { })[name];
if (value == null) { value = nullish; } if (value == null) { value = nullish; }
if (typeof(value) !== "number") { if (typeof(value) !== "number") {
logger.throwArgumentError(`invalud value for ${ name }`, "costs", costs); throwArgumentError(`invalud value for ${ name }`, "costs", costs);
} }
props[name] = value; props[name] = value;
} }

@ -1,9 +1,9 @@
import { defineProperties } from "../utils/properties.js"; import {
import { FetchRequest } from "../utils/fetch.js"; defineProperties, FetchRequest, throwArgumentError, throwError
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js"; import { showThrottleMessage } from "./community.js";
import { logger } from "../utils/logger.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js"; import { JsonRpcProvider } from "./provider-jsonrpc.js";
@ -40,7 +40,7 @@ function getHost(name: string): string {
return "opt-kovan.g.alchemy.com"; return "opt-kovan.g.alchemy.com";
} }
return logger.throwArgumentError("unsupported network", "network", name); return throwArgumentError("unsupported network", "network", name);
} }
export class AlchemyProvider extends JsonRpcProvider implements CommunityResourcable { export class AlchemyProvider extends JsonRpcProvider implements CommunityResourcable {
@ -79,14 +79,14 @@ export class AlchemyProvider extends JsonRpcProvider implements CommunityResourc
if (data) { if (data) {
if (error) { if (error) {
logger.throwError("an error occurred during transaction executions", "CALL_EXCEPTION", { throwError("an error occurred during transaction executions", "CALL_EXCEPTION", {
data data
}); });
} }
return data; return data;
} }
return logger.throwError("could not parse trace result", "BAD_DATA", { value: trace }); return throwError("could not parse trace result", "BAD_DATA", { value: trace });
} }
return await super._perform(req); return await super._perform(req);

@ -1,9 +1,9 @@
import { defineProperties } from "../utils/properties.js"; import {
import { FetchRequest } from "../utils/fetch.js"; defineProperties, FetchRequest, throwArgumentError
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js"; import { AbstractProvider } from "./abstract-provider.js";
import { showThrottleMessage } from "./community.js"; import { showThrottleMessage } from "./community.js";
import { logger } from "../utils/logger.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js"; import { JsonRpcProvider } from "./provider-jsonrpc.js";
@ -28,7 +28,7 @@ function getHost(name: string): string {
case "arbitrum": case "arbitrum":
return "rpc.ankr.com/arbitrum"; return "rpc.ankr.com/arbitrum";
} }
return logger.throwArgumentError("unsupported network", "network", name); return throwArgumentError("unsupported network", "network", name);
} }

@ -1,5 +1,5 @@
import { throwArgumentError } from "../utils/index.js";
import { logger } from "../utils/logger.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js"; import { JsonRpcProvider } from "./provider-jsonrpc.js";
@ -10,7 +10,7 @@ export class CloudflareProvider extends JsonRpcProvider {
constructor(_network: Networkish = "homestead") { constructor(_network: Networkish = "homestead") {
const network = Network.from(_network); const network = Network.from(_network);
if (network.name !== "homestead") { if (network.name !== "homestead") {
return logger.throwArgumentError("unsupported network", "network", _network); return throwArgumentError("unsupported network", "network", _network);
} }
super("https:/\/cloudflare-eth.com/", network, { staticNetwork: network }); super("https:/\/cloudflare-eth.com/", network, { staticNetwork: network });
} }

@ -1,11 +1,10 @@
import { hexlify, isHexString } from "../utils/data.js"; import {
import { toQuantity } from "../utils/maths.js"; defineProperties,
import { isError } from "../utils/errors.js"; hexlify, toQuantity,
import { defineProperties } from "../utils/properties.js"; FetchRequest,
import { toUtf8String } from "../utils/utf8.js"; throwArgumentError, throwError,
import { FetchRequest } from "../utils/fetch.js"; toUtf8String
} from "../utils/index.js";
if (false) { console.log(isHexString, isError); } // @TODO
import { AbstractProvider } from "./abstract-provider.js"; import { AbstractProvider } from "./abstract-provider.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
@ -17,8 +16,6 @@ import type { Networkish } from "./network.js";
//import type { } from "./pagination"; //import type { } from "./pagination";
import type { TransactionRequest } from "./provider.js"; import type { TransactionRequest } from "./provider.js";
import { logger } from "../utils/logger.js";
const defaultApiKey = "9D13ZE7XSBTJ94N9BNJ2MA33VMAY2YPIRB"; const defaultApiKey = "9D13ZE7XSBTJ94N9BNJ2MA33VMAY2YPIRB";
@ -82,7 +79,7 @@ export class EtherscanProvider extends AbstractProvider {
default: default:
} }
return logger.throwArgumentError("unsupported network", "network", this.network); return throwArgumentError("unsupported network", "network", this.network);
} }
getUrl(module: string, params: Record<string, string>): string { getUrl(module: string, params: Record<string, string>): string {
@ -358,7 +355,7 @@ export class EtherscanProvider extends AbstractProvider {
}); });
} }
return logger.throwError("getBlock by blockHash not supported by Etherscan", "UNSUPPORTED_OPERATION", { return throwError("getBlock by blockHash not supported by Etherscan", "UNSUPPORTED_OPERATION", {
operation: "getBlock(blockHash)" operation: "getBlock(blockHash)"
}); });

@ -1,8 +1,9 @@
import { hexlify } from "../utils/data.js"; import {
getBigInt, getNumber, hexlify, throwError, throwArgumentError
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js"; import { AbstractProvider } from "./abstract-provider.js";
import { logger } from "../utils/logger.js";
import { Network } from "./network.js" import { Network } from "./network.js"
import type { Frozen } from "../utils/index.js"; import type { Frozen } from "../utils/index.js";
@ -23,7 +24,7 @@ function shuffle<T = any>(array: Array<T>): void {
} }
} }
function stall(duration: number) { function stall(duration: number): Promise<void> {
return new Promise((resolve) => { setTimeout(resolve, duration); }); return new Promise((resolve) => { setTimeout(resolve, duration); });
} }
@ -134,15 +135,15 @@ type RunningState = {
function normalize(network: Frozen<Network>, value: any, req: PerformActionRequest): string { function normalize(network: Frozen<Network>, value: any, req: PerformActionRequest): string {
switch (req.method) { switch (req.method) {
case "chainId": case "chainId":
return logger.getBigInt(value).toString(); return getBigInt(value).toString();
case "getBlockNumber": case "getBlockNumber":
return logger.getNumber(value).toString(); return getNumber(value).toString();
case "getGasPrice": case "getGasPrice":
return logger.getBigInt(value).toString(); return getBigInt(value).toString();
case "getBalance": case "getBalance":
return logger.getBigInt(value).toString(); return getBigInt(value).toString();
case "getTransactionCount": case "getTransactionCount":
return logger.getNumber(value).toString(); return getNumber(value).toString();
case "getCode": case "getCode":
return hexlify(value); return hexlify(value);
case "getStorageAt": case "getStorageAt":
@ -159,12 +160,12 @@ function normalize(network: Frozen<Network>, value: any, req: PerformActionReque
case "call": case "call":
return hexlify(value); return hexlify(value);
case "estimateGas": case "estimateGas":
return logger.getBigInt(value).toString(); return getBigInt(value).toString();
case "getLogs": case "getLogs":
return JSON.stringify(value.map((v: any) => network.formatter.log(v))); return JSON.stringify(value.map((v: any) => network.formatter.log(v)));
} }
return logger.throwError("unsupported method", "UNSUPPORTED_OPERATION", { return throwError("unsupported method", "UNSUPPORTED_OPERATION", {
operation: `_perform(${ JSON.stringify(req.method) })` operation: `_perform(${ JSON.stringify(req.method) })`
}); });
} }
@ -220,7 +221,7 @@ function getMedian(results: Array<TallyResult>): bigint {
} }
function getFuzzyMode(quorum: number, results: Array<TallyResult>): undefined | number { function getFuzzyMode(quorum: number, results: Array<TallyResult>): undefined | number {
if (quorum === 1) { return logger.getNumber(getMedian(results), "%internal"); } if (quorum === 1) { return getNumber(getMedian(results), "%internal"); }
const tally: Map<number, { result: number, weight: number }> = new Map(); const tally: Map<number, { result: number, weight: number }> = new Map();
const add = (result: number, weight: number) => { const add = (result: number, weight: number) => {
@ -230,7 +231,7 @@ function getFuzzyMode(quorum: number, results: Array<TallyResult>): undefined |
}; };
for (const { weight, result } of results) { for (const { weight, result } of results) {
const r = logger.getNumber(result); const r = getNumber(result);
add(r - 1, weight); add(r - 1, weight);
add(r, weight); add(r, weight);
add(r + 1, weight); add(r + 1, weight);
@ -282,7 +283,7 @@ export class FallbackProvider extends AbstractProvider {
this.eventWorkers = 1; this.eventWorkers = 1;
if (this.quorum > this.#configs.reduce((a, c) => (a + c.weight), 0)) { if (this.quorum > this.#configs.reduce((a, c) => (a + c.weight), 0)) {
logger.throwArgumentError("quorum exceed provider wieght", "quorum", this.quorum); throwArgumentError("quorum exceed provider wieght", "quorum", this.quorum);
} }
} }
@ -291,8 +292,8 @@ export class FallbackProvider extends AbstractProvider {
return this.#configs.slice(); return this.#configs.slice();
} }
async _detectNetwork() { async _detectNetwork(): Promise<Frozen<Network>> {
return Network.from(logger.getBigInt(await this._perform({ method: "chainId" }))).freeze(); return Network.from(getBigInt(await this._perform({ method: "chainId" }))).freeze();
} }
// @TODO: Add support to select providers to be the event subscriber // @TODO: Add support to select providers to be the event subscriber
@ -382,7 +383,7 @@ export class FallbackProvider extends AbstractProvider {
if (chainId == null) { if (chainId == null) {
chainId = network.chainId; chainId = network.chainId;
} else if (network.chainId !== chainId) { } else if (network.chainId !== chainId) {
logger.throwError("cannot mix providers on different networks", "UNSUPPORTED_OPERATION", { throwError("cannot mix providers on different networks", "UNSUPPORTED_OPERATION", {
operation: "new FallbackProvider" operation: "new FallbackProvider"
}); });
} }
@ -417,9 +418,9 @@ export class FallbackProvider extends AbstractProvider {
case "getBlockNumber": { case "getBlockNumber": {
// We need to get the bootstrap block height // We need to get the bootstrap block height
if (this.#height === -2) { if (this.#height === -2) {
const height = Math.ceil(logger.getNumber(getMedian(this.#configs.map((c) => ({ const height = Math.ceil(getNumber(getMedian(this.#configs.map((c) => ({
result: c.blockNumber, result: c.blockNumber,
normal: logger.getNumber(c.blockNumber).toString(), normal: getNumber(c.blockNumber).toString(),
weight: c.weight weight: c.weight
}))), "%internal")); }))), "%internal"));
this.#height = height; this.#height = height;
@ -461,7 +462,7 @@ export class FallbackProvider extends AbstractProvider {
throw new Error("TODO"); throw new Error("TODO");
} }
return logger.throwError("unsupported method", "UNSUPPORTED_OPERATION", { return throwError("unsupported method", "UNSUPPORTED_OPERATION", {
operation: `_perform(${ JSON.stringify((<any>req).method) })` operation: `_perform(${ JSON.stringify((<any>req).method) })`
}); });
} }

@ -1,8 +1,8 @@
import { defineProperties } from "../utils/properties.js"; import {
import { FetchRequest } from "../utils/fetch.js"; defineProperties, FetchRequest, throwArgumentError
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js"; import { showThrottleMessage } from "./community.js";
import { logger } from "../utils/logger.js";
import { Network } from "./network.js"; import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js"; import { JsonRpcProvider } from "./provider-jsonrpc.js";
@ -39,7 +39,7 @@ function getHost(name: string): string {
return "arbitrum-rinkeby.infura.io"; return "arbitrum-rinkeby.infura.io";
} }
return logger.throwArgumentError("unsupported network", "network", name); return throwArgumentError("unsupported network", "network", name);
} }

@ -4,13 +4,13 @@
// https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false // https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false
import { resolveAddress } from "../address/index.js"; import { resolveAddress } from "../address/index.js";
import { hexlify } from "../utils/data.js";
import { FetchRequest } from "../utils/fetch.js";
import { toQuantity } from "../utils/maths.js";
import { defineProperties } from "../utils/properties.js";
import { TypedDataEncoder } from "../hash/typed-data.js"; import { TypedDataEncoder } from "../hash/typed-data.js";
import { toUtf8Bytes } from "../utils/utf8.js";
import { accessListify } from "../transaction/index.js"; import { accessListify } from "../transaction/index.js";
import {
defineProperties, getBigInt, hexlify, toQuantity, toUtf8Bytes,
throwArgumentError, throwError,
FetchRequest
} from "../utils/index.js";
import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js"; import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js";
import { AbstractSigner } from "./abstract-signer.js"; import { AbstractSigner } from "./abstract-signer.js";
@ -25,8 +25,6 @@ import type { Networkish } from "./network.js";
import type { Provider, TransactionRequest, TransactionResponse } from "./provider.js"; import type { Provider, TransactionRequest, TransactionResponse } from "./provider.js";
import type { Signer } from "./signer.js"; import type { Signer } from "./signer.js";
import { logger } from "../utils/logger.js";
//function copy<T = any>(value: T): T { //function copy<T = any>(value: T): T {
// return JSON.parse(JSON.stringify(value)); // return JSON.parse(JSON.stringify(value));
@ -149,7 +147,7 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
} }
connect(provider: null | Provider): Signer { connect(provider: null | Provider): Signer {
return logger.throwError("cannot reconnect JsonRpcSigner", "UNSUPPORTED_OPERATION", { return throwError("cannot reconnect JsonRpcSigner", "UNSUPPORTED_OPERATION", {
operation: "signer.connect" operation: "signer.connect"
}); });
} }
@ -196,7 +194,7 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
promises.push((async () => { promises.push((async () => {
const from = await resolveAddress(_from, this.provider); const from = await resolveAddress(_from, this.provider);
if (from == null || from.toLowerCase() !== this.address.toLowerCase()) { if (from == null || from.toLowerCase() !== this.address.toLowerCase()) {
logger.throwArgumentError("from address mismatch", "transaction", _tx); throwArgumentError("from address mismatch", "transaction", _tx);
} }
tx.from = from; tx.from = from;
})()); })());
@ -263,7 +261,7 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
if (tx.from) { if (tx.from) {
const from = await resolveAddress(tx.from, this.provider); const from = await resolveAddress(tx.from, this.provider);
if (from == null || from.toLowerCase() !== this.address.toLowerCase()) { if (from == null || from.toLowerCase() !== this.address.toLowerCase()) {
return logger.throwArgumentError("from address mismatch", "transaction", _tx); return throwArgumentError("from address mismatch", "transaction", _tx);
} }
tx.from = from; tx.from = from;
} else { } else {
@ -288,7 +286,7 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (value: string) => { const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (value: string) => {
const address = await resolveAddress(value); const address = await resolveAddress(value);
if (address == null) { if (address == null) {
return logger.throwArgumentError("TypedData does not support null address", "value", value); return throwArgumentError("TypedData does not support null address", "value", value);
} }
return address; return address;
}); });
@ -337,7 +335,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
// This could be relaxed in the future to just check equivalent networks // This could be relaxed in the future to just check equivalent networks
const staticNetwork = this._getOption("staticNetwork"); const staticNetwork = this._getOption("staticNetwork");
if (staticNetwork && staticNetwork !== network) { if (staticNetwork && staticNetwork !== network) {
logger.throwArgumentError("staticNetwork MUST match network object", "options", options); throwArgumentError("staticNetwork MUST match network object", "options", options);
} }
} }
@ -445,7 +443,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
// Sub-classes MUST override this // Sub-classes MUST override this
_send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult | JsonRpcError>> { _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult | JsonRpcError>> {
return logger.throwError("sub-classes must override _send", "UNSUPPORTED_OPERATION", { return throwError("sub-classes must override _send", "UNSUPPORTED_OPERATION", {
operation: "jsonRpcApiProvider._send" operation: "jsonRpcApiProvider._send"
}); });
} }
@ -481,7 +479,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
const network = this._getOption("staticNetwork"); const network = this._getOption("staticNetwork");
if (network) { return network; } if (network) { return network; }
return Network.from(logger.getBigInt(await this._perform({ method: "chainId" }))); return Network.from(getBigInt(await this._perform({ method: "chainId" })));
} }
_getSubscriber(sub: Subscription): Subscriber { _getSubscriber(sub: Subscription): Subscriber {
@ -510,7 +508,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
if ((<any>tx)[key] == null) { return; } if ((<any>tx)[key] == null) { return; }
let dstKey = key; let dstKey = key;
if (key === "gasLimit") { dstKey = "gas"; } if (key === "gasLimit") { dstKey = "gas"; }
(<any>result)[dstKey] = toQuantity(logger.getBigInt((<any>tx)[key], `tx.${ key }`)); (<any>result)[dstKey] = toQuantity(getBigInt((<any>tx)[key], `tx.${ key }`));
}); });
// Make sure addresses and data are lowercase // Make sure addresses and data are lowercase
@ -693,7 +691,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
// is fair), so we delete type if it is 0 and a non-EIP-1559 network // is fair), so we delete type if it is 0 and a non-EIP-1559 network
if (req.method === "call" || req.method === "estimateGas") { if (req.method === "call" || req.method === "estimateGas") {
let tx = req.transaction; let tx = req.transaction;
if (tx && tx.type != null && logger.getBigInt(tx.type)) { if (tx && tx.type != null && getBigInt(tx.type)) {
// If there are no EIP-1559 properties, it might be non-EIP-a559 // If there are no EIP-1559 properties, it might be non-EIP-a559
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) { if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
const feeData = await this.getFeeData(); const feeData = await this.getFeeData();

@ -10,7 +10,7 @@
*/ */
import { UnmanagedSubscriber } from "./abstract-provider.js"; import { UnmanagedSubscriber } from "./abstract-provider.js";
import { assertArgument, logger } from "../utils/logger.js"; import { assertArgument, makeError, throwError } from "../utils/index.js";
import { JsonRpcApiProvider } from "./provider-jsonrpc.js"; import { JsonRpcApiProvider } from "./provider-jsonrpc.js";
import type { Subscriber, Subscription } from "./abstract-provider.js"; import type { Subscriber, Subscription } from "./abstract-provider.js";
@ -65,7 +65,7 @@ export class SocketSubscriber implements Subscriber {
// and resume // and resume
pause(dropWhilePaused?: boolean): void { pause(dropWhilePaused?: boolean): void {
if (!dropWhilePaused) { if (!dropWhilePaused) {
logger.throwError("preserve logs while paused not supported by SocketSubscriber yet", "UNSUPPORTED_OPERATION", { throwError("preserve logs while paused not supported by SocketSubscriber yet", "UNSUPPORTED_OPERATION", {
operation: "pause(false)" operation: "pause(false)"
}); });
} }
@ -228,7 +228,7 @@ export class SocketProvider extends JsonRpcApiProvider {
if ("error" in result) { if ("error" in result) {
const { message, code, data } = result.error; const { message, code, data } = result.error;
const error = logger.makeError(message || "unkonwn error", "SERVER_ERROR", { const error = makeError(message || "unkonwn error", "SERVER_ERROR", {
request: `ws:${ JSON.stringify(callback.payload) }`, request: `ws:${ JSON.stringify(callback.payload) }`,
info: { code, data } info: { code, data }
}); });

@ -1,10 +1,9 @@
//import { resolveAddress } from "@ethersproject/address"; //import { resolveAddress } from "@ethersproject/address";
import { hexlify } from "../utils/data.js"; import {
import { logger } from "../utils/logger.js"; defineProperties, getBigInt, getNumber, hexlify, throwError
import { defineProperties } from "../utils/properties.js"; } from "../utils/index.js";
import { accessListify } from "../transaction/index.js"; import { accessListify } from "../transaction/index.js";
import type { AddressLike, NameResolver } from "../address/index.js"; import type { AddressLike, NameResolver } from "../address/index.js";
import type { BigNumberish, EventEmitterable, Frozen, Listener } from "../utils/index.js"; import type { BigNumberish, EventEmitterable, Frozen, Listener } from "../utils/index.js";
import type { Signature } from "../crypto/index.js"; import type { Signature } from "../crypto/index.js";
@ -127,13 +126,13 @@ export function copyRequest(req: CallRequest): PreparedRequest {
const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerGas, maxPriorityFeePerGas,value".split(/,/); const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerGas, maxPriorityFeePerGas,value".split(/,/);
for (const key in bigIntKeys) { for (const key in bigIntKeys) {
if (!(key in req) || (<any>req)[key] == null) { continue; } if (!(key in req) || (<any>req)[key] == null) { continue; }
result[key] = logger.getBigInt((<any>req)[key], `request.${ key }`); result[key] = getBigInt((<any>req)[key], `request.${ key }`);
} }
const numberKeys = "type,nonce".split(/,/); const numberKeys = "type,nonce".split(/,/);
for (const key in numberKeys) { for (const key in numberKeys) {
if (!(key in req) || (<any>req)[key] == null) { continue; } if (!(key in req) || (<any>req)[key] == null) { continue; }
result[key] = logger.getNumber((<any>req)[key], `request.${ key }`); result[key] = getNumber((<any>req)[key], `request.${ key }`);
} }
if (req.accessList) { if (req.accessList) {
@ -585,7 +584,7 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable<Lo
reorderedEvent(other?: TransactionResponse): OrphanFilter { reorderedEvent(other?: TransactionResponse): OrphanFilter {
if (other && !other.isMined()) { if (other && !other.isMined()) {
return logger.throwError("unmined 'other' transction cannot be orphaned", "UNSUPPORTED_OPERATION", { return throwError("unmined 'other' transction cannot be orphaned", "UNSUPPORTED_OPERATION", {
operation: "reorderedEvent(other)" }); operation: "reorderedEvent(other)" });
} }
return createReorderedTransactionFilter(this, other); return createReorderedTransactionFilter(this, other);
@ -775,7 +774,7 @@ export class TransactionResponse implements TransactionLike<string>, Transaction
removedEvent(): OrphanFilter { removedEvent(): OrphanFilter {
if (!this.isMined()) { if (!this.isMined()) {
return logger.throwError("unmined transaction canot be orphaned", "UNSUPPORTED_OPERATION", { return throwError("unmined transaction canot be orphaned", "UNSUPPORTED_OPERATION", {
operation: "removeEvent()" }); operation: "removeEvent()" });
} }
return createRemovedTransactionFilter(this); return createRemovedTransactionFilter(this);
@ -783,11 +782,11 @@ export class TransactionResponse implements TransactionLike<string>, Transaction
reorderedEvent(other?: TransactionResponse): OrphanFilter { reorderedEvent(other?: TransactionResponse): OrphanFilter {
if (!this.isMined()) { if (!this.isMined()) {
return logger.throwError("unmined transaction canot be orphaned", "UNSUPPORTED_OPERATION", { return throwError("unmined transaction canot be orphaned", "UNSUPPORTED_OPERATION", {
operation: "removeEvent()" }); operation: "removeEvent()" });
} }
if (other && !other.isMined()) { if (other && !other.isMined()) {
return logger.throwError("unmined 'other' transaction canot be orphaned", "UNSUPPORTED_OPERATION", { return throwError("unmined 'other' transaction canot be orphaned", "UNSUPPORTED_OPERATION", {
operation: "removeEvent()" }); operation: "removeEvent()" });
} }
return createReorderedTransactionFilter(this, other); return createReorderedTransactionFilter(this, other);
@ -1006,7 +1005,7 @@ export interface Provider extends ContractRunner, EventEmitterable<ProviderEvent
waitForBlock(blockTag?: BlockTag): Promise<Block<string>>; waitForBlock(blockTag?: BlockTag): Promise<Block<string>>;
} }
// @TODO: I think I can drop T
function fail<T>(): T { function fail<T>(): T {
throw new Error("this provider should not be used"); throw new Error("this provider should not be used");
} }
@ -1014,64 +1013,64 @@ function fail<T>(): T {
class DummyProvider implements Provider { class DummyProvider implements Provider {
get provider(): this { return this; } get provider(): this { return this; }
async getNetwork() { return fail<Frozen<Network>>(); } async getNetwork(): Promise<Frozen<Network>> { return fail<Frozen<Network>>(); }
async getFeeData() { return fail<FeeData>(); } async getFeeData(): Promise<FeeData> { return fail<FeeData>(); }
async estimateGas(tx: TransactionRequest) { return fail<bigint>(); } async estimateGas(tx: TransactionRequest): Promise<bigint> { return fail<bigint>(); }
async call(tx: CallRequest) { return fail<string>(); } async call(tx: CallRequest): Promise<string> { return fail<string>(); }
async resolveName(name: string) { return fail<null | string>(); } async resolveName(name: string): Promise<null | string> { return fail<null | string>(); }
// State // State
async getBlockNumber() { return fail<number>(); } async getBlockNumber(): Promise<number> { return fail<number>(); }
// Account // Account
async getBalance(address: AddressLike, blockTag?: BlockTag) { async getBalance(address: AddressLike, blockTag?: BlockTag): Promise<bigint> {
return fail<bigint>(); return fail<bigint>();
} }
async getTransactionCount(address: AddressLike, blockTag?: BlockTag) { async getTransactionCount(address: AddressLike, blockTag?: BlockTag): Promise<number> {
return fail<number>(); return fail<number>();
} }
async getCode(address: AddressLike, blockTag?: BlockTag) { async getCode(address: AddressLike, blockTag?: BlockTag): Promise<string> {
return fail<string>(); return fail<string>();
} }
async getStorageAt(address: AddressLike, position: BigNumberish, blockTag?: BlockTag) { async getStorageAt(address: AddressLike, position: BigNumberish, blockTag?: BlockTag): Promise<string> {
return fail<string>(); return fail<string>();
} }
// Write // Write
async broadcastTransaction(signedTx: string) { return fail<TransactionResponse>(); } async broadcastTransaction(signedTx: string): Promise<TransactionResponse> { return fail<TransactionResponse>(); }
// Queries // Queries
async getBlock(blockHashOrBlockTag: BlockTag | string){ async getBlock(blockHashOrBlockTag: BlockTag | string): Promise<null | Block<string>>{
return fail<null | Block<string>>(); return fail<null | Block<string>>();
} }
async getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string) { async getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string): Promise<null | Block<TransactionResponse>> {
return fail<null | Block<TransactionResponse>>(); return fail<null | Block<TransactionResponse>>();
} }
async getTransaction(hash: string) { async getTransaction(hash: string): Promise<null | TransactionResponse> {
return fail<null | TransactionResponse>(); return fail<null | TransactionResponse>();
} }
async getTransactionReceipt(hash: string) { async getTransactionReceipt(hash: string): Promise<null | TransactionReceipt> {
return fail<null | TransactionReceipt>(); return fail<null | TransactionReceipt>();
} }
// Bloom-filter Queries // Bloom-filter Queries
async getLogs(filter: Filter | FilterByBlockHash) { async getLogs(filter: Filter | FilterByBlockHash): Promise<Array<Log>> {
return fail<Array<Log>>(); return fail<Array<Log>>();
} }
// ENS // ENS
async lookupAddress(address: string) { async lookupAddress(address: string): Promise<null | string> {
return fail<null | string>(); return fail<null | string>();
} }
async waitForTransaction(hash: string, confirms?: number, timeout?: number) { async waitForTransaction(hash: string, confirms?: number, timeout?: number): Promise<null | TransactionReceipt> {
return fail<null | TransactionReceipt>(); return fail<null | TransactionReceipt>();
} }
async waitForBlock(blockTag?: BlockTag) { async waitForBlock(blockTag?: BlockTag): Promise<Block<string>> {
return fail<Block<string>>(); return fail<Block<string>>();
} }

@ -1,6 +1,7 @@
import { getNumber } from "../utils/index.js";
import type { Subscriber } from "./abstract-provider.js"; import type { Subscriber } from "./abstract-provider.js";
import { logger } from "../utils/logger.js";
//#TODO: Temp //#TODO: Temp
@ -25,7 +26,7 @@ export class BlockConnectionSubscriber implements Subscriber {
start(): void { start(): void {
this.#filterId = this.#provider._subscribe([ "newHeads" ], (result: any) => { this.#filterId = this.#provider._subscribe([ "newHeads" ], (result: any) => {
const blockNumber = logger.getNumber(result.number); const blockNumber = getNumber(result.number);
const initial = (this.#blockNumber === -2) ? blockNumber: (this.#blockNumber + 1) const initial = (this.#blockNumber === -2) ? blockNumber: (this.#blockNumber + 1)
for (let b = initial; b <= blockNumber; b++) { for (let b = initial; b <= blockNumber; b++) {
this.#provider.emit("block", b); this.#provider.emit("block", b);

@ -2,7 +2,7 @@ import { isHexString } from "../utils/data.js";
import type { AbstractProvider, Subscriber } from "./abstract-provider.js"; import type { AbstractProvider, Subscriber } from "./abstract-provider.js";
import type { EventFilter, OrphanFilter, ProviderEvent } from "./provider.js"; import type { EventFilter, OrphanFilter, ProviderEvent } from "./provider.js";
import { logger } from "../utils/logger.js"; import { throwError } from "../utils/index.js";
function copy(obj: any): any { function copy(obj: any): any {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
@ -12,7 +12,7 @@ export function getPollingSubscriber(provider: AbstractProvider, event: Provider
if (event === "block") { return new PollingBlockSubscriber(provider); } if (event === "block") { return new PollingBlockSubscriber(provider); }
if (isHexString(event, 32)) { return new PollingTransactionSubscriber(provider, event); } if (isHexString(event, 32)) { return new PollingTransactionSubscriber(provider, event); }
return logger.throwError("unsupported polling event", "UNSUPPORTED_OPERATION", { return throwError("unsupported polling event", "UNSUPPORTED_OPERATION", {
operation: "getPollingSubscriber", info: { event } operation: "getPollingSubscriber", info: { event }
}); });
} }

@ -2,7 +2,8 @@
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { keccak256, Signature } from "../crypto/index.js"; import { keccak256, Signature } from "../crypto/index.js";
import { import {
concat, decodeRlp, encodeRlp, getStore, hexlify, logger, setStore, toArray, zeroPadValue concat, decodeRlp, encodeRlp, getBytes, getStore, getBigInt, getNumber, hexlify,
setStore, throwArgumentError, toArray, zeroPadValue
} from "../utils/index.js"; } from "../utils/index.js";
import { accessListify } from "./accesslist.js"; import { accessListify } from "./accesslist.js";
@ -55,7 +56,7 @@ function handleData(value: string, param: string): string {
try { try {
return hexlify(value); return hexlify(value);
} catch (error) { } catch (error) {
return logger.throwArgumentError("invalid data", param, value); return throwArgumentError("invalid data", param, value);
} }
} }
@ -63,27 +64,27 @@ function handleAccessList(value: any, param: string): AccessList {
try { try {
return accessListify(value); return accessListify(value);
} catch (error) { } catch (error) {
return logger.throwArgumentError("invalid accessList", param, value); return throwArgumentError("invalid accessList", param, value);
} }
} }
function handleNumber(_value: string, param: string): number { function handleNumber(_value: string, param: string): number {
if (_value === "0x") { return 0; } if (_value === "0x") { return 0; }
return logger.getNumber(_value, param); return getNumber(_value, param);
} }
function handleUint(_value: string, param: string): bigint { function handleUint(_value: string, param: string): bigint {
if (_value === "0x") { return BN_0; } if (_value === "0x") { return BN_0; }
const value = logger.getBigInt(_value, param); const value = getBigInt(_value, param);
if (value > BN_MAX_UINT) { logger.throwArgumentError("value exceeds uint size", param, value); } if (value > BN_MAX_UINT) { throwArgumentError("value exceeds uint size", param, value); }
return value; return value;
} }
function formatNumber(_value: BigNumberish, name: string): Uint8Array { function formatNumber(_value: BigNumberish, name: string): Uint8Array {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
const result = toArray(value); const result = toArray(value);
if (result.length > 32) { if (result.length > 32) {
logger.throwArgumentError(`value too large`, `tx.${ name }`, value); throwArgumentError(`value too large`, `tx.${ name }`, value);
} }
return result; return result;
} }
@ -96,7 +97,7 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
const fields: any = decodeRlp(data); const fields: any = decodeRlp(data);
if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 6)) { if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 6)) {
return logger.throwArgumentError("invalid field count for legacy transaction", "data", data); return throwArgumentError("invalid field count for legacy transaction", "data", data);
} }
const tx: TransactionLike = { const tx: TransactionLike = {
@ -130,7 +131,7 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
// Signed Legacy Transaction // Signed Legacy Transaction
if (chainId === BN_0 && (v < BN_27 || v > BN_28)) { if (chainId === BN_0 && (v < BN_27 || v > BN_28)) {
logger.throwArgumentError("non-canonical legacy v", "v", fields[6]); throwArgumentError("non-canonical legacy v", "v", fields[6]);
} }
tx.signature = Signature.from({ tx.signature = Signature.from({
@ -158,12 +159,12 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string {
let chainId = BN_0; let chainId = BN_0;
if (tx.chainId != null) { if (tx.chainId != null) {
// A chainId was provided; if non-zero we'll use EIP-155 // A chainId was provided; if non-zero we'll use EIP-155
chainId = logger.getBigInt(tx.chainId, "tx.chainId"); chainId = getBigInt(tx.chainId, "tx.chainId");
// We have a chainId in the tx and an EIP-155 v in the signature, // We have a chainId in the tx and an EIP-155 v in the signature,
// make sure they agree with each other // make sure they agree with each other
if (sig && sig.networkV != null && sig.legacyChainId !== chainId) { if (sig && sig.networkV != null && sig.legacyChainId !== chainId) {
logger.throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig); throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig);
} }
} else if (sig) { } else if (sig) {
@ -189,7 +190,7 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string {
if (chainId !== BN_0) { if (chainId !== BN_0) {
v = Signature.getChainIdV(chainId, sig.v); v = Signature.getChainIdV(chainId, sig.v);
} else if (BigInt(sig.v) !== v) { } else if (BigInt(sig.v) !== v) {
logger.throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig); throwArgumentError("tx.chainId/sig.v mismatch", "sig", sig);
} }
fields.push(toArray(v)); fields.push(toArray(v));
@ -205,7 +206,7 @@ function _parseEipSignature(tx: TransactionLike, fields: Array<string>, serializ
yParity = handleNumber(fields[0], "yParity"); yParity = handleNumber(fields[0], "yParity");
if (yParity !== 0 && yParity !== 1) { throw new Error("bad yParity"); } if (yParity !== 0 && yParity !== 1) { throw new Error("bad yParity"); }
} catch (error) { } catch (error) {
return logger.throwArgumentError("invalid yParity", "yParity", fields[0]); return throwArgumentError("invalid yParity", "yParity", fields[0]);
} }
const r = zeroPadValue(fields[1], 32); const r = zeroPadValue(fields[1], 32);
@ -216,10 +217,10 @@ function _parseEipSignature(tx: TransactionLike, fields: Array<string>, serializ
} }
function _parseEip1559(data: Uint8Array): TransactionLike { function _parseEip1559(data: Uint8Array): TransactionLike {
const fields: any = decodeRlp(logger.getBytes(data).slice(1)); const fields: any = decodeRlp(getBytes(data).slice(1));
if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 12)) { if (!Array.isArray(fields) || (fields.length !== 9 && fields.length !== 12)) {
logger.throwArgumentError("invalid field count for transaction type: 2", "data", hexlify(data)); throwArgumentError("invalid field count for transaction type: 2", "data", hexlify(data));
} }
const maxPriorityFeePerGas = handleUint(fields[2], "maxPriorityFeePerGas"); const maxPriorityFeePerGas = handleUint(fields[2], "maxPriorityFeePerGas");
@ -271,10 +272,10 @@ function _serializeEip1559(tx: TransactionLike, sig?: Signature): string {
} }
function _parseEip2930(data: Uint8Array): TransactionLike { function _parseEip2930(data: Uint8Array): TransactionLike {
const fields: any = decodeRlp(logger.getBytes(data).slice(1)); const fields: any = decodeRlp(getBytes(data).slice(1));
if (!Array.isArray(fields) || (fields.length !== 8 && fields.length !== 11)) { if (!Array.isArray(fields) || (fields.length !== 8 && fields.length !== 11)) {
logger.throwArgumentError("invalid field count for transaction type: 1", "data", hexlify(data)); throwArgumentError("invalid field count for transaction type: 1", "data", hexlify(data));
} }
const tx: TransactionLike = { const tx: TransactionLike = {
@ -397,10 +398,10 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
} }
get nonce(): number { return getStore(this.#props, "nonce"); } get nonce(): number { return getStore(this.#props, "nonce"); }
set nonce(value: BigNumberish) { setStore(this.#props, "nonce", logger.getNumber(value, "value")); } set nonce(value: BigNumberish) { setStore(this.#props, "nonce", getNumber(value, "value")); }
get gasLimit(): bigint { return getStore(this.#props, "gasLimit"); } get gasLimit(): bigint { return getStore(this.#props, "gasLimit"); }
set gasLimit(value: BigNumberish) { setStore(this.#props, "gasLimit", logger.getBigInt(value)); } set gasLimit(value: BigNumberish) { setStore(this.#props, "gasLimit", getBigInt(value)); }
get gasPrice(): null | bigint { get gasPrice(): null | bigint {
const value = getStore(this.#props, "gasPrice"); const value = getStore(this.#props, "gasPrice");
@ -408,7 +409,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return value; return value;
} }
set gasPrice(value: null | BigNumberish) { set gasPrice(value: null | BigNumberish) {
setStore(this.#props, "gasPrice", (value == null) ? null: logger.getBigInt(value, "gasPrice")); setStore(this.#props, "gasPrice", (value == null) ? null: getBigInt(value, "gasPrice"));
} }
get maxPriorityFeePerGas(): null | bigint { get maxPriorityFeePerGas(): null | bigint {
@ -417,7 +418,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return value; return value;
} }
set maxPriorityFeePerGas(value: null | BigNumberish) { set maxPriorityFeePerGas(value: null | BigNumberish) {
setStore(this.#props, "maxPriorityFeePerGas", (value == null) ? null: logger.getBigInt(value, "maxPriorityFeePerGas")); setStore(this.#props, "maxPriorityFeePerGas", (value == null) ? null: getBigInt(value, "maxPriorityFeePerGas"));
} }
get maxFeePerGas(): null | bigint { get maxFeePerGas(): null | bigint {
@ -426,7 +427,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
return value; return value;
} }
set maxFeePerGas(value: null | BigNumberish) { set maxFeePerGas(value: null | BigNumberish) {
setStore(this.#props, "maxFeePerGas", (value == null) ? null: logger.getBigInt(value, "maxFeePerGas")); setStore(this.#props, "maxFeePerGas", (value == null) ? null: getBigInt(value, "maxFeePerGas"));
} }
get data(): string { return getStore(this.#props, "data"); } get data(): string { return getStore(this.#props, "data"); }
@ -434,11 +435,11 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
get value(): bigint { return getStore(this.#props, "value"); } get value(): bigint { return getStore(this.#props, "value"); }
set value(value: BigNumberish) { set value(value: BigNumberish) {
setStore(this.#props, "value", logger.getBigInt(value, "value")); setStore(this.#props, "value", getBigInt(value, "value"));
} }
get chainId(): bigint { return getStore(this.#props, "chainId"); } get chainId(): bigint { return getStore(this.#props, "chainId"); }
set chainId(value: BigNumberish) { setStore(this.#props, "chainId", logger.getBigInt(value)); } set chainId(value: BigNumberish) { setStore(this.#props, "chainId", getBigInt(value)); }
get signature(): null | Signature { return getStore(this.#props, "sig") || null; } get signature(): null | Signature { return getStore(this.#props, "sig") || null; }
set signature(value: null | SignatureLike) { set signature(value: null | SignatureLike) {
@ -624,7 +625,7 @@ export class Transaction implements Freezable<Transaction>, TransactionLike<stri
static from(tx: string | TransactionLike<string>): Transaction { static from(tx: string | TransactionLike<string>): Transaction {
if (typeof(tx) === "string") { if (typeof(tx) === "string") {
const payload = logger.getBytes(tx); const payload = getBytes(tx);
if (payload[0] >= 0x7f) { // @TODO: > vs >= ?? if (payload[0] >= 0x7f) { // @TODO: > vs >= ??
return Transaction.from(_parseLegacy(payload)); return Transaction.from(_parseLegacy(payload));

@ -1,5 +1,6 @@
import { logger } from "./logger.js"; import { getBytes } from "./data.js";
import { throwArgumentError } from "./errors.js";
import { toBigInt, toHex } from "./maths.js"; import { toBigInt, toHex } from "./maths.js";
import type { BytesLike } from "./index.js"; import type { BytesLike } from "./index.js";
@ -17,7 +18,7 @@ function getAlpha(letter: string): bigint {
} }
const result = Lookup[letter]; const result = Lookup[letter];
if (result == null) { if (result == null) {
logger.throwArgumentError(`invalid base58 value`, "letter", letter); throwArgumentError(`invalid base58 value`, "letter", letter);
} }
return result; return result;
} }
@ -30,7 +31,7 @@ const BN_58 = BigInt(58);
* Encode %%value%% as Base58-encoded data. * Encode %%value%% as Base58-encoded data.
*/ */
export function encodeBase58(_value: BytesLike): string { export function encodeBase58(_value: BytesLike): string {
let value = toBigInt(logger.getBytes(_value)); let value = toBigInt(getBytes(_value));
let result = ""; let result = "";
while (value) { while (value) {
result = Alphabet[Number(value % BN_58)] + result; result = Alphabet[Number(value % BN_58)] + result;
@ -42,11 +43,11 @@ export function encodeBase58(_value: BytesLike): string {
/** /**
* Decode the Base58-encoded %%value%%. * Decode the Base58-encoded %%value%%.
*/ */
export function decodeBase58(value: string): string { export function decodeBase58(value: string): Uint8Array {
let result = BN_0; let result = BN_0;
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
result *= BN_58; result *= BN_58;
result += getAlpha(value[i]); result += getAlpha(value[i]);
} }
return toHex(result); return getBytes(toHex(result));
} }

@ -1,7 +1,7 @@
// utils/base64-browser // utils/base64-browser
import { logger } from "./logger.js"; import { getBytes } from "./data.js";
import type { BytesLike } from "./data.js"; import type { BytesLike } from "./data.js";
@ -12,11 +12,11 @@ export function decodeBase64(textData: string): Uint8Array {
for (let i = 0; i < textData.length; i++) { for (let i = 0; i < textData.length; i++) {
data[i] = textData.charCodeAt(i); data[i] = textData.charCodeAt(i);
} }
return logger.getBytes(data); return getBytes(data);
} }
export function encodeBase64(_data: BytesLike): string { export function encodeBase64(_data: BytesLike): string {
const data = logger.getBytes(_data); const data = getBytes(_data);
let textData = ""; let textData = "";
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
textData += String.fromCharCode(data[i]); textData += String.fromCharCode(data[i]);

@ -1,12 +1,12 @@
import { logger } from "./logger.js"; import { getBytes, getBytesCopy } from "./data.js";
import type { BytesLike } from "./data.js"; import type { BytesLike } from "./data.js";
export function decodeBase64(textData: string): Uint8Array { export function decodeBase64(textData: string): Uint8Array {
return logger.getBytesCopy(Buffer.from(textData, "base64")); return getBytesCopy(Buffer.from(textData, "base64"));
}; };
export function encodeBase64(data: BytesLike): string { export function encodeBase64(data: BytesLike): string {
return Buffer.from(logger.getBytes(data)).toString("base64"); return Buffer.from(getBytes(data)).toString("base64");
} }

@ -1,9 +1,37 @@
import { logger } from "./logger.js"; import { throwArgumentError, throwError } from "./errors.js";
export type BytesLike = string | Uint8Array; export type BytesLike = string | Uint8Array;
function _getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array {
if (value instanceof Uint8Array) {
if (copy) { return new Uint8Array(value); }
return value;
}
export function isHexString(value: any, length?: number | boolean): value is string { if (typeof(value) === "string" && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) {
const result = new Uint8Array((value.length - 2) / 2);
let offset = 2;
for (let i = 0; i < result.length; i++) {
result[i] = parseInt(value.substring(offset, offset + 2), 16);
offset += 2;
}
return result;
}
return throwArgumentError("invalid BytesLike value", name || "value", value);
}
export function getBytes(value: BytesLike, name?: string): Uint8Array {
return _getBytes(value, name, false);
}
export function getBytesCopy(value: BytesLike, name?: string): Uint8Array {
return _getBytes(value, name, true);
}
export function isHexString(value: any, length?: number | boolean): value is `0x${ string }` {
if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) { if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) {
return false return false
} }
@ -20,7 +48,7 @@ export function isBytesLike(value: any): value is BytesLike {
const HexCharacters: string = "0123456789abcdef"; const HexCharacters: string = "0123456789abcdef";
export function hexlify(data: BytesLike): string { export function hexlify(data: BytesLike): string {
const bytes = logger.getBytes(data); const bytes = getBytes(data);
let result = "0x"; let result = "0x";
for (let i = 0; i < bytes.length; i++) { for (let i = 0; i < bytes.length; i++) {
@ -36,12 +64,12 @@ export function concat(datas: ReadonlyArray<BytesLike>): string {
export function dataLength(data: BytesLike): number { export function dataLength(data: BytesLike): number {
if (isHexString(data, true)) { return (data.length - 2) / 2; } if (isHexString(data, true)) { return (data.length - 2) / 2; }
return logger.getBytes(data).length; return getBytes(data).length;
} }
export function dataSlice(data: BytesLike, start?: number, end?: number): string { export function dataSlice(data: BytesLike, start?: number, end?: number): string {
const bytes = logger.getBytes(data); const bytes = getBytes(data);
if (end != null && end > bytes.length) { logger.throwError("cannot slice beyond data bounds", "BUFFER_OVERRUN", { if (end != null && end > bytes.length) { throwError("cannot slice beyond data bounds", "BUFFER_OVERRUN", {
buffer: bytes, length: bytes.length, offset: end buffer: bytes, length: bytes.length, offset: end
}); } }); }
return hexlify(bytes.slice((start == null) ? 0: start, (end == null) ? bytes.length: end)); return hexlify(bytes.slice((start == null) ? 0: start, (end == null) ? bytes.length: end));
@ -55,9 +83,9 @@ export function stripZerosLeft(data: BytesLike): string {
function zeroPad(data: BytesLike, length: number, left: boolean): string { function zeroPad(data: BytesLike, length: number, left: boolean): string {
const bytes = logger.getBytes(data); const bytes = getBytes(data);
if (length < bytes.length) { if (length < bytes.length) {
logger.throwError("padding exceeds data length", "BUFFER_OVERRUN", { throwError("padding exceeds data length", "BUFFER_OVERRUN", {
buffer: new Uint8Array(bytes), buffer: new Uint8Array(bytes),
length: length, length: length,
offset: length + 1 offset: length + 1

@ -1,5 +1,8 @@
//export type TransactionReceipt { //export type TransactionReceipt {
//} //}
import { version } from "../_version.js";
import { defineReadOnly } from "./properties.js";
export type ErrorSignature = { export type ErrorSignature = {
r: string; r: string;
@ -10,6 +13,13 @@ export type ErrorSignature = {
export type ErrorAccessList = Array<{ address: string, storageKeys: Array<string> }>; export type ErrorAccessList = Array<{ address: string, storageKeys: Array<string> }>;
export type ErrorInfo<T> = Omit<T, "code" | "name" | "message">;
// The type of error to use for various error codes
const ErrorConstructors: Record<string, { new (...args: Array<any>): Error }> = { };
ErrorConstructors.INVALID_ARGUMENT = TypeError;
ErrorConstructors.NUMERIC_FAULT = RangeError;
ErrorConstructors.BUFFER_OVERRUN = RangeError;
/* /*
export interface ErrorTransaction { export interface ErrorTransaction {
type?: number; type?: number;
@ -237,6 +247,12 @@ export interface ActionRejectedError extends EthersError<"ACTION_REJECTED"> {
// Coding; converts an ErrorCode its Typed Error // Coding; converts an ErrorCode its Typed Error
/**
* A conditional type that transforms the [[ErrorCode]] T into
* its EthersError type.
*
* @flatworm-skip-docs
*/
export type CodedEthersError<T> = export type CodedEthersError<T> =
T extends "UNKNOWN_ERROR" ? UnknownError: T extends "UNKNOWN_ERROR" ? UnknownError:
T extends "NOT_IMPLEMENTED" ? NotImplementedError: T extends "NOT_IMPLEMENTED" ? NotImplementedError:
@ -267,6 +283,8 @@ export type CodedEthersError<T> =
never; never;
/** /**
* try { * try {
* // code.... * // code....
@ -283,3 +301,111 @@ export function isError<K extends ErrorCode, T extends CodedEthersError<K>>(erro
export function isCallException(error: any): error is CallExceptionError { export function isCallException(error: any): error is CallExceptionError {
return isError(error, "CALL_EXCEPTION"); return isError(error, "CALL_EXCEPTION");
} }
export function makeError<K extends ErrorCode, T extends CodedEthersError<K>>(message: string, code: K, info?: ErrorInfo<T>): T {
{
const details: Array<string> = [];
if (info) {
for (const key in info) {
const value = <any>(info[<keyof ErrorInfo<T>>key]);
try {
details.push(key + "=" + JSON.stringify(value));
} catch (error) {
details.push(key + "=[could not serialize object]");
}
}
}
details.push(`code=${ code }`);
details.push(`version=${ version }`);
if (details.length) {
message += " (" + details.join(", ") + ")";
}
}
const create = ErrorConstructors[code] || Error;
const error = <T>(new create(message));
defineReadOnly(error, "code", code);
if (info) {
for (const key in info) {
defineReadOnly(error, <keyof T>key, <any>(info[<keyof ErrorInfo<T>>key]));
}
}
return <T>error;
}
export function throwError<K extends ErrorCode, T extends CodedEthersError<K>>(message: string, code: K, info?: ErrorInfo<T>): never {
throw makeError(message, code, info);
}
export function throwArgumentError(message: string, name: string, value: any): never {
return throwError(message, "INVALID_ARGUMENT", {
argument: name,
value: value
});
}
export function assertArgument(check: unknown, message: string, name: string, value: unknown): asserts check {
if (!check) { throwArgumentError(message, name, value); }
}
export function assertArgumentCount(count: number, expectedCount: number, message: string = ""): void {
if (message) { message = ": " + message; }
if (count < expectedCount) {
throwError("missing arguemnt" + message, "MISSING_ARGUMENT", {
count: count,
expectedCount: expectedCount
});
}
if (count > expectedCount) {
throwError("too many arguemnts" + message, "UNEXPECTED_ARGUMENT", {
count: count,
expectedCount: expectedCount
});
}
}
const _normalizeForms = ["NFD", "NFC", "NFKD", "NFKC"].reduce((accum, form) => {
try {
// General test for normalize
/* c8 ignore start */
if ("test".normalize(form) !== "test") { throw new Error("bad"); };
/* c8 ignore stop */
if (form === "NFD") {
const check = String.fromCharCode(0xe9).normalize("NFD");
const expected = String.fromCharCode(0x65, 0x0301)
/* c8 ignore start */
if (check !== expected) { throw new Error("broken") }
/* c8 ignore stop */
}
accum.push(form);
} catch(error) { }
return accum;
}, <Array<string>>[]);
export function assertNormalize(form: string): void {
if (_normalizeForms.indexOf(form) === -1) {
throwError("platform missing String.prototype.normalize", "UNSUPPORTED_OPERATION", {
operation: "String.prototype.normalize", info: { form }
});
}
}
export function assertPrivate(givenGuard: any, guard: any, className: string = ""): void {
if (givenGuard !== guard) {
let method = className, operation = "new";
if (className) {
method += ".";
operation += " " + className;
}
throwError(`private constructor; use ${ method }from* methods`, "UNSUPPORTED_OPERATION", {
operation
});
}
}

@ -1,6 +1,6 @@
import { decodeBase64, encodeBase64 } from "./base64.js"; import { decodeBase64, encodeBase64 } from "./base64.js";
import { hexlify } from "./data.js"; import { hexlify } from "./data.js";
import { assertArgument, logger } from "./logger.js"; import { assertArgument, throwArgumentError, throwError } from "./errors.js";
import { defineProperties } from "./properties.js"; import { defineProperties } from "./properties.js";
import { toUtf8Bytes, toUtf8String } from "./utf8.js" import { toUtf8Bytes, toUtf8String } from "./utf8.js"
@ -114,7 +114,7 @@ export class FetchCancelSignal {
addListener(listener: () => void): void { addListener(listener: () => void): void {
if (this.#cancelled) { if (this.#cancelled) {
logger.throwError("singal already cancelled", "UNSUPPORTED_OPERATION", { throwError("singal already cancelled", "UNSUPPORTED_OPERATION", {
operation: "fetchCancelSignal.addCancelListener" operation: "fetchCancelSignal.addCancelListener"
}); });
} }
@ -125,7 +125,7 @@ export class FetchCancelSignal {
checkSignal(): void { checkSignal(): void {
if (!this.cancelled) { return; } if (!this.cancelled) { return; }
logger.throwError("cancelled", "CANCELLED", { }); throwError("cancelled", "CANCELLED", { });
} }
} }
@ -251,7 +251,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
} }
setCredentials(username: string, password: string): void { setCredentials(username: string, password: string): void {
if (username.match(/:/)) { if (username.match(/:/)) {
logger.throwArgumentError("invalid basic authentication username", "username", "[REDACTED]"); throwArgumentError("invalid basic authentication username", "username", "[REDACTED]");
} }
this.#creds = `${ username }:${ password }`; this.#creds = `${ username }:${ password }`;
} }
@ -319,7 +319,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
} }
if (getTime() > expires) { if (getTime() > expires) {
return logger.throwError("timeout", "TIMEOUT", { return throwError("timeout", "TIMEOUT", {
operation: "request.send", reason: "timeout", request: _request operation: "request.send", reason: "timeout", request: _request
}); });
} }
@ -409,7 +409,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
send(): Promise<FetchResponse> { send(): Promise<FetchResponse> {
if (this.#signal != null) { if (this.#signal != null) {
return logger.throwError("request already sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.send" }); return throwError("request already sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.send" });
} }
this.#signal = new FetchCancelSignal(this); this.#signal = new FetchCancelSignal(this);
return this.#send(0, getTime() + this.timeout, 0, this, new FetchResponse(0, "", { }, null, this)); return this.#send(0, getTime() + this.timeout, 0, this, new FetchResponse(0, "", { }, null, this));
@ -417,7 +417,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
cancel(): void { cancel(): void {
if (this.#signal == null) { if (this.#signal == null) {
return logger.throwError("request has not been sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.cancel" }); return throwError("request has not been sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.cancel" });
} }
const signal = fetchSignals.get(this); const signal = fetchSignals.get(this);
if (!signal) { throw new Error("missing signal; should not happen"); } if (!signal) { throw new Error("missing signal; should not happen"); }
@ -438,7 +438,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
// - downgrading the security (e.g. https => http) // - downgrading the security (e.g. https => http)
// - to non-HTTP (or non-HTTPS) protocols [this could be relaxed?] // - to non-HTTP (or non-HTTPS) protocols [this could be relaxed?]
if (this.method !== "GET" || (current === "https" && target === "http") || !location.match(/^https?:/)) { if (this.method !== "GET" || (current === "https" && target === "http") || !location.match(/^https?:/)) {
return logger.throwError(`unsupported redirect`, "UNSUPPORTED_OPERATION", { return throwError(`unsupported redirect`, "UNSUPPORTED_OPERATION", {
operation: `redirect(${ this.method } ${ JSON.stringify(this.url) } => ${ JSON.stringify(location) })` operation: `redirect(${ this.method } ${ JSON.stringify(this.url) } => ${ JSON.stringify(location) })`
}); });
} }
@ -536,7 +536,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
get statusCode(): number { return this.#statusCode; } get statusCode(): number { return this.#statusCode; }
get statusMessage(): string { return this.#statusMessage; } get statusMessage(): string { return this.#statusMessage; }
get headers() { return this.#headers; } get headers(): Record<string, string> { return this.#headers; }
get body(): null | Readonly<Uint8Array> { get body(): null | Readonly<Uint8Array> {
return (this.#body == null) ? null: new Uint8Array(this.#body); return (this.#body == null) ? null: new Uint8Array(this.#body);
} }
@ -544,7 +544,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
try { try {
return (this.#body == null) ? "": toUtf8String(this.#body); return (this.#body == null) ? "": toUtf8String(this.#body);
} catch (error) { } catch (error) {
return logger.throwError("response body is not valid UTF-8 data", "UNSUPPORTED_OPERATION", { return throwError("response body is not valid UTF-8 data", "UNSUPPORTED_OPERATION", {
operation: "bodyText", info: { response: this } operation: "bodyText", info: { response: this }
}); });
} }
@ -553,7 +553,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
try { try {
return JSON.parse(this.bodyText); return JSON.parse(this.bodyText);
} catch (error) { } catch (error) {
return logger.throwError("response body is not valid JSON", "UNSUPPORTED_OPERATION", { return throwError("response body is not valid JSON", "UNSUPPORTED_OPERATION", {
operation: "bodyJson", info: { response: this } operation: "bodyJson", info: { response: this }
}); });
} }
@ -607,7 +607,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
if (stall == null) { if (stall == null) {
stall = -1; stall = -1;
} else if (typeof(stall) !== "number" || !Number.isInteger(stall) || stall < 0) { } else if (typeof(stall) !== "number" || !Number.isInteger(stall) || stall < 0) {
return logger.throwArgumentError("invalid stall timeout", "stall", stall); return throwArgumentError("invalid stall timeout", "stall", stall);
} }
const error = new Error(message || "throttling requests"); const error = new Error(message || "throttling requests");
@ -637,7 +637,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
if (message === "") { if (message === "") {
message = `server response ${ this.statusCode } ${ this.statusMessage }`; message = `server response ${ this.statusCode } ${ this.statusMessage }`;
} }
logger.throwError(message, "SERVER_ERROR", { throwError(message, "SERVER_ERROR", {
request: (this.request || "unknown request"), response: this, error request: (this.request || "unknown request"), response: this, error
}); });
} }

@ -1,5 +1,6 @@
import { logger } from "./logger.js"; import { getBytes } from "./data.js";
import { fromTwos, toBigInt, toHex, toTwos } from "./maths.js"; import { throwArgumentError, throwError } from "./errors.js";
import { getBigInt, getNumber, fromTwos, toBigInt, toHex, toTwos } from "./maths.js";
import type { BigNumberish, BytesLike, Numeric } from "./index.js"; import type { BigNumberish, BytesLike, Numeric } from "./index.js";
@ -11,7 +12,7 @@ const NegativeOne = BigInt(-1);
function throwFault(message: string, fault: string, operation: string, value?: any): never { function throwFault(message: string, fault: string, operation: string, value?: any): never {
const params: any = { fault: fault, operation: operation }; const params: any = { fault: fault, operation: operation };
if (value !== undefined) { params.value = value; } if (value !== undefined) { params.value = value; }
return logger.throwError(message, "NUMERIC_FAULT", params); return throwError(message, "NUMERIC_FAULT", params);
} }
// Constant to pull zeros from for multipliers // Constant to pull zeros from for multipliers
@ -22,7 +23,7 @@ while (zeros.length < 256) { zeros += zeros; }
function getMultiplier(decimals: number): bigint { function getMultiplier(decimals: number): bigint {
if (typeof(decimals) !== "number" || decimals < 0 || decimals > 256 || decimals % 1 ) { if (typeof(decimals) !== "number" || decimals < 0 || decimals > 256 || decimals % 1 ) {
logger.throwArgumentError("invalid decimal length", "decimals", decimals); throwArgumentError("invalid decimal length", "decimals", decimals);
} }
return BigInt("1" + zeros.substring(0, decimals)); return BigInt("1" + zeros.substring(0, decimals));
@ -31,8 +32,8 @@ function getMultiplier(decimals: number): bigint {
export function formatFixed(_value: BigNumberish, _decimals?: Numeric): string { export function formatFixed(_value: BigNumberish, _decimals?: Numeric): string {
if (_decimals == null) { _decimals = 18; } if (_decimals == null) { _decimals = 18; }
let value = logger.getBigInt(_value, "value"); let value = getBigInt(_value, "value");
const decimals = logger.getNumber(_decimals, "decimals"); const decimals = getNumber(_decimals, "decimals");
const multiplier = getMultiplier(decimals); const multiplier = getMultiplier(decimals);
const multiplierStr = String(multiplier); const multiplierStr = String(multiplier);
@ -60,12 +61,12 @@ export function formatFixed(_value: BigNumberish, _decimals?: Numeric): string {
export function parseFixed(value: string, _decimals: Numeric): bigint { export function parseFixed(value: string, _decimals: Numeric): bigint {
if (_decimals == null) { _decimals = 18; } if (_decimals == null) { _decimals = 18; }
const decimals = logger.getNumber(_decimals, "decimals"); const decimals = getNumber(_decimals, "decimals");
const multiplier = getMultiplier(decimals); const multiplier = getMultiplier(decimals);
if (typeof(value) !== "string" || !value.match(/^-?[0-9.]+$/)) { if (typeof(value) !== "string" || !value.match(/^-?[0-9.]+$/)) {
logger.throwArgumentError("invalid decimal value", "value", value); throwArgumentError("invalid decimal value", "value", value);
} }
// Is it negative? // Is it negative?
@ -73,13 +74,13 @@ export function parseFixed(value: string, _decimals: Numeric): bigint {
if (negative) { value = value.substring(1); } if (negative) { value = value.substring(1); }
if (value === ".") { if (value === ".") {
logger.throwArgumentError("missing value", "value", value); throwArgumentError("missing value", "value", value);
} }
// Split it into a whole and fractional part // Split it into a whole and fractional part
const comps = value.split("."); const comps = value.split(".");
if (comps.length > 2) { if (comps.length > 2) {
logger.throwArgumentError("too many decimal points", "value", value); throwArgumentError("too many decimal points", "value", value);
} }
let whole = (comps[0] || "0"), fraction = (comps[1] || "0"); let whole = (comps[0] || "0"), fraction = (comps[1] || "0");
@ -110,7 +111,6 @@ export function parseFixed(value: string, _decimals: Numeric): bigint {
return wei; return wei;
} }
export class FixedFormat { export class FixedFormat {
readonly signed: boolean; readonly signed: boolean;
readonly width: number; readonly width: number;
@ -121,7 +121,7 @@ export class FixedFormat {
constructor(constructorGuard: any, signed: boolean, width: number, decimals: number) { constructor(constructorGuard: any, signed: boolean, width: number, decimals: number) {
if (constructorGuard !== _constructorGuard) { if (constructorGuard !== _constructorGuard) {
logger.throwError("cannot use FixedFormat constructor; use FixedFormat.from", "UNSUPPORTED_OPERATION", { throwError("cannot use FixedFormat constructor; use FixedFormat.from", "UNSUPPORTED_OPERATION", {
operation: "new FixedFormat" operation: "new FixedFormat"
}); });
} }
@ -156,7 +156,7 @@ export class FixedFormat {
} else { } else {
const match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/); const match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/);
if (!match) { if (!match) {
return logger.throwArgumentError("invalid fixed format", "format", value); return throwArgumentError("invalid fixed format", "format", value);
} }
signed = (match[1] !== "u"); signed = (match[1] !== "u");
width = parseInt(match[2]); width = parseInt(match[2]);
@ -166,7 +166,7 @@ export class FixedFormat {
const check = (key: string, type: string, defaultValue: any): any => { const check = (key: string, type: string, defaultValue: any): any => {
if (value[key] == null) { return defaultValue; } if (value[key] == null) { return defaultValue; }
if (typeof(value[key]) !== type) { if (typeof(value[key]) !== type) {
logger.throwArgumentError("invalid fixed format (" + key + " not " + type +")", "format." + key, value[key]); throwArgumentError("invalid fixed format (" + key + " not " + type +")", "format." + key, value[key]);
} }
return value[key]; return value[key];
} }
@ -176,17 +176,20 @@ export class FixedFormat {
} }
if (width % 8) { if (width % 8) {
logger.throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width); throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width);
} }
if (decimals > 80) { if (decimals > 80) {
logger.throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals); throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals);
} }
return new FixedFormat(_constructorGuard, signed, width, decimals); return new FixedFormat(_constructorGuard, signed, width, decimals);
} }
} }
/**
* Fixed Number class
*/
export class FixedNumber { export class FixedNumber {
readonly format: FixedFormat; readonly format: FixedFormat;
@ -197,7 +200,7 @@ export class FixedNumber {
constructor(constructorGuard: any, hex: string, value: string, format?: FixedFormat) { constructor(constructorGuard: any, hex: string, value: string, format?: FixedFormat) {
if (constructorGuard !== _constructorGuard) { if (constructorGuard !== _constructorGuard) {
logger.throwError("cannot use FixedNumber constructor; use FixedNumber.from", "UNSUPPORTED_OPERATION", { throwError("cannot use FixedNumber constructor; use FixedNumber.from", "UNSUPPORTED_OPERATION", {
operation: "new FixedFormat" operation: "new FixedFormat"
}); });
} }
@ -213,10 +216,14 @@ export class FixedNumber {
#checkFormat(other: FixedNumber): void { #checkFormat(other: FixedNumber): void {
if (this.format.name !== other.format.name) { if (this.format.name !== other.format.name) {
logger.throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other); throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other);
} }
} }
/**
* Returns a new [[FixedNumber]] with the result of this added
* to %%other%%.
*/
addUnsafe(other: FixedNumber): FixedNumber { addUnsafe(other: FixedNumber): FixedNumber {
this.#checkFormat(other); this.#checkFormat(other);
const a = parseFixed(this.#value, this.format.decimals); const a = parseFixed(this.#value, this.format.decimals);
@ -282,7 +289,7 @@ export class FixedNumber {
if (comps.length === 1) { comps.push("0"); } if (comps.length === 1) { comps.push("0"); }
if (decimals < 0 || decimals > 80 || (decimals % 1)) { if (decimals < 0 || decimals > 80 || (decimals % 1)) {
logger.throwArgumentError("invalid decimal count", "decimals", decimals); throwArgumentError("invalid decimal count", "decimals", decimals);
} }
if (comps[1].length <= decimals) { return this; } if (comps[1].length <= decimals) { return this; }
@ -324,7 +331,7 @@ export class FixedNumber {
} }
static fromValue(value: BigNumberish, decimals = 0, format: FixedFormat | string | number = "fixed"): FixedNumber { static fromValue(value: BigNumberish, decimals: number = 0, format: FixedFormat | string | number = "fixed"): FixedNumber {
return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format)); return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format));
} }
@ -350,7 +357,7 @@ export class FixedNumber {
} }
static fromBytes(_value: BytesLike, format: FixedFormat | string | number = "fixed"): FixedNumber { static fromBytes(_value: BytesLike, format: FixedFormat | string | number = "fixed"): FixedNumber {
const value = logger.getBytes(_value, "value"); const value = getBytes(_value, "value");
const fixedFormat = FixedFormat.from(format); const fixedFormat = FixedFormat.from(format);
if (value.length > fixedFormat.width / 8) { if (value.length > fixedFormat.width / 8) {
@ -366,7 +373,7 @@ export class FixedNumber {
return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat); return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat);
} }
static from(value: any, format?: FixedFormat | string | number) { static from(value: any, format?: FixedFormat | string | number): FixedNumber {
if (typeof(value) === "string") { if (typeof(value) === "string") {
return FixedNumber.fromString(value, format); return FixedNumber.fromString(value, format);
} }
@ -384,7 +391,7 @@ export class FixedNumber {
} }
} }
return logger.throwArgumentError("invalid FixedNumber value", "value", value); return throwArgumentError("invalid FixedNumber value", "value", value);
} }
static isFixedNumber(value: any): value is FixedNumber { static isFixedNumber(value: any): value is FixedNumber {

@ -1,4 +1,4 @@
import { logger } from "./logger.js"; import { throwError } from "./errors.js";
import type { FetchRequest, FetchCancelSignal, GetUrlResponse } from "./fetch.js"; import type { FetchRequest, FetchCancelSignal, GetUrlResponse } from "./fetch.js";
@ -31,14 +31,14 @@ export async function getUrl(req: FetchRequest, _signal?: FetchCancelSignal): Pr
const protocol = req.url.split(":")[0].toLowerCase(); const protocol = req.url.split(":")[0].toLowerCase();
if (protocol !== "http" && protocol !== "https") { if (protocol !== "http" && protocol !== "https") {
logger.throwError(`unsupported protocol ${ protocol }`, "UNSUPPORTED_OPERATION", { throwError(`unsupported protocol ${ protocol }`, "UNSUPPORTED_OPERATION", {
info: { protocol }, info: { protocol },
operation: "request" operation: "request"
}); });
} }
if (req.credentials && !req.allowInsecureAuthentication) { if (req.credentials && !req.allowInsecureAuthentication) {
logger.throwError("insecure authorized connections unsupported", "UNSUPPORTED_OPERATION", { throwError("insecure authorized connections unsupported", "UNSUPPORTED_OPERATION", {
operation: "request" operation: "request"
}); });
} }

@ -2,7 +2,8 @@ import http from "http";
import https from "https"; import https from "https";
import { gunzipSync } from "zlib"; import { gunzipSync } from "zlib";
import { logger } from "./logger.js"; import { throwError } from "./errors.js";
import { getBytes } from "./data.js";
import type { FetchRequest, FetchCancelSignal, GetUrlResponse } from "./fetch.js"; import type { FetchRequest, FetchCancelSignal, GetUrlResponse } from "./fetch.js";
@ -12,14 +13,14 @@ export async function getUrl(req: FetchRequest, signal?: FetchCancelSignal): Pro
const protocol = req.url.split(":")[0].toLowerCase(); const protocol = req.url.split(":")[0].toLowerCase();
if (protocol !== "http" && protocol !== "https") { if (protocol !== "http" && protocol !== "https") {
logger.throwError(`unsupported protocol ${ protocol }`, "UNSUPPORTED_OPERATION", { throwError(`unsupported protocol ${ protocol }`, "UNSUPPORTED_OPERATION", {
info: { protocol }, info: { protocol },
operation: "request" operation: "request"
}); });
} }
if (req.credentials && !req.allowInsecureAuthentication) { if (req.credentials && !req.allowInsecureAuthentication) {
logger.throwError("insecure authorized connections unsupported", "UNSUPPORTED_OPERATION", { throwError("insecure authorized connections unsupported", "UNSUPPORTED_OPERATION", {
operation: "request" operation: "request"
}); });
} }
@ -78,7 +79,7 @@ export async function getUrl(req: FetchRequest, signal?: FetchCancelSignal): Pro
resp.on("end", () => { resp.on("end", () => {
if (headers["content-encoding"] === "gzip" && body) { if (headers["content-encoding"] === "gzip" && body) {
body = logger.getBytes(gunzipSync(body)); body = getBytes(gunzipSync(body));
} }
resolve({ statusCode, statusMessage, headers, body }); resolve({ statusCode, statusMessage, headers, body });

@ -19,11 +19,15 @@ export { decodeBase58, encodeBase58 } from "./base58.js";
export { decodeBase64, encodeBase64 } from "./base64.js"; export { decodeBase64, encodeBase64 } from "./base64.js";
export { export {
isHexString, isBytesLike, hexlify, concat, dataLength, dataSlice, getBytes, getBytesCopy, isHexString, isBytesLike, hexlify, concat, dataLength, dataSlice,
stripZerosLeft, zeroPadValue, zeroPadBytes stripZerosLeft, zeroPadValue, zeroPadBytes
} from "./data.js"; } from "./data.js";
export { isCallException, isError } from "./errors.js" export {
isCallException, isError,
makeError, throwError, throwArgumentError,
assertArgument, assertArgumentCount, assertPrivate, assertNormalize
} from "./errors.js"
export { EventPayload } from "./events.js"; export { EventPayload } from "./events.js";
@ -31,11 +35,9 @@ export { FetchRequest, FetchResponse } from "./fetch.js";
export { FixedFormat, FixedNumber, formatFixed, parseFixed } from "./fixednumber.js" export { FixedFormat, FixedNumber, formatFixed, parseFixed } from "./fixednumber.js"
export { assertArgument, Logger, logger } from "./logger.js";
export { export {
fromTwos, toTwos, mask, fromTwos, toTwos, mask,
toBigInt, toNumber, toHex, toArray, toQuantity getBigInt, getNumber, toBigInt, toNumber, toHex, toArray, toQuantity
} from "./maths.js"; } from "./maths.js";
export { resolveProperties, defineReadOnly, defineProperties} from "./properties.js"; export { resolveProperties, defineReadOnly, defineProperties} from "./properties.js";

@ -1,36 +1,13 @@
/*
import { version } from "../_version.js"; import { version } from "../_version.js";
import type { BigNumberish, BytesLike } from "./index.js"; import { throwArgumentError } from "./errors.js";
import type { CodedEthersError, ErrorCode } from "./errors.js";
export type ErrorInfo<T> = Omit<T, "code" | "name" | "message">;
export type LogLevel = "debug" | "info" | "warning" | "error" | "off"; export type LogLevel = "debug" | "info" | "warning" | "error" | "off";
const LogLevels: Array<LogLevel> = [ "debug", "info", "warning", "error", "off" ]; const LogLevels: Array<LogLevel> = [ "debug", "info", "warning", "error", "off" ];
const _normalizeForms = ["NFD", "NFC", "NFKD", "NFKC"].reduce((accum, form) => {
try {
// General test for normalize
/* c8 ignore start */
if ("test".normalize(form) !== "test") { throw new Error("bad"); };
/* c8 ignore stop */
if (form === "NFD") {
const check = String.fromCharCode(0xe9).normalize("NFD");
const expected = String.fromCharCode(0x65, 0x0301)
/* c8 ignore start */
if (check !== expected) { throw new Error("broken") }
/* c8 ignore stop */
}
accum.push(form);
} catch(error) { }
return accum;
}, <Array<string>>[]);
function defineReadOnly<T, P extends keyof T>(object: T, name: P, value: T[P]): void { function defineReadOnly<T, P extends keyof T>(object: T, name: P, value: T[P]): void {
Object.defineProperty(object, name, { Object.defineProperty(object, name, {
@ -38,15 +15,6 @@ function defineReadOnly<T, P extends keyof T>(object: T, name: P, value: T[P]):
}); });
} }
// IEEE 754 support 53-bits of mantissa
const maxValue = 0x1fffffffffffff;
// The type of error to use for various error codes
const ErrorConstructors: Record<string, { new (...args: Array<any>): Error }> = { };
ErrorConstructors.INVALID_ARGUMENT = TypeError;
ErrorConstructors.NUMERIC_FAULT = RangeError;
ErrorConstructors.BUFFER_OVERRUN = RangeError;
export type AssertFunc<T> = () => (undefined | T); export type AssertFunc<T> = () => (undefined | T);
export class Logger { export class Logger {
@ -66,168 +34,15 @@ export class Logger {
set logLevel(value: LogLevel) { set logLevel(value: LogLevel) {
const logLevel = LogLevels.indexOf(value); const logLevel = LogLevels.indexOf(value);
if (logLevel == null) { if (logLevel == null) {
this.throwArgumentError("invalid logLevel", "logLevel", value); throwArgumentError("invalid logLevel", "logLevel", value);
} }
this.#logLevel = logLevel; this.#logLevel = logLevel;
} }
makeError<K extends ErrorCode, T extends CodedEthersError<K>>(message: string, code: K, info?: ErrorInfo<T>): T {
{
const details: Array<string> = [];
if (info) {
for (const key in info) {
const value = <any>(info[<keyof ErrorInfo<T>>key]);
try {
details.push(key + "=" + JSON.stringify(value));
} catch (error) {
details.push(key + "=[could not serialize object]");
}
}
}
details.push(`code=${ code }`);
details.push(`version=${ this.version }`);
if (details.length) {
message += " (" + details.join(", ") + ")";
}
}
const create = ErrorConstructors[code] || Error;
const error = <T>(new create(message));
defineReadOnly(error, "code", code);
if (info) {
for (const key in info) {
defineReadOnly(error, <keyof T>key, <any>(info[<keyof ErrorInfo<T>>key]));
}
}
return <T>error;
}
throwError<K extends ErrorCode, T extends CodedEthersError<K>>(message: string, code: K, info?: ErrorInfo<T>): never {
throw this.makeError(message, code, info);
}
throwArgumentError(message: string, name: string, value: any): never {
return this.throwError(message, "INVALID_ARGUMENT", {
argument: name,
value: value
});
}
assertNormalize(form: string): void {
if (_normalizeForms.indexOf(form) === -1) {
this.throwError("platform missing String.prototype.normalize", "UNSUPPORTED_OPERATION", {
operation: "String.prototype.normalize", info: { form }
});
}
}
assertPrivate(givenGuard: any, guard: any, className = ""): void {
if (givenGuard !== guard) {
let method = className, operation = "new";
if (className) {
method += ".";
operation += " " + className;
}
this.throwError(`private constructor; use ${ method }from* methods`, "UNSUPPORTED_OPERATION", {
operation
});
}
}
assertArgumentCount(count: number, expectedCount: number, message: string = ""): void {
if (message) { message = ": " + message; }
if (count < expectedCount) {
this.throwError("missing arguemnt" + message, "MISSING_ARGUMENT", {
count: count,
expectedCount: expectedCount
});
}
if (count > expectedCount) {
this.throwError("too many arguemnts" + message, "UNEXPECTED_ARGUMENT", {
count: count,
expectedCount: expectedCount
});
}
}
#getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array {
if (value instanceof Uint8Array) {
if (copy) { return new Uint8Array(value); }
return value;
}
if (typeof(value) === "string" && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) {
const result = new Uint8Array((value.length - 2) / 2);
let offset = 2;
for (let i = 0; i < result.length; i++) {
result[i] = parseInt(value.substring(offset, offset + 2), 16);
offset += 2;
}
return result;
}
return this.throwArgumentError("invalid BytesLike value", name || "value", value);
}
getBytes(value: BytesLike, name?: string): Uint8Array {
return this.#getBytes(value, name, false);
}
getBytesCopy(value: BytesLike, name?: string): Uint8Array {
return this.#getBytes(value, name, true);
}
getNumber(value: BigNumberish, name?: string): number {
switch (typeof(value)) {
case "bigint":
if (value < -maxValue || value > maxValue) {
this.throwArgumentError("overflow", name || "value", value);
}
return Number(value);
case "number":
if (!Number.isInteger(value)) {
this.throwArgumentError("underflow", name || "value", value);
} else if (value < -maxValue || value > maxValue) {
this.throwArgumentError("overflow", name || "value", value);
}
return value;
case "string":
try {
return this.getNumber(BigInt(value), name);
} catch(e: any) {
this.throwArgumentError(`invalid numeric string: ${ e.message }`, name || "value", value);
}
}
return this.throwArgumentError("invalid numeric value", name || "value", value);
}
getBigInt(value: BigNumberish, name?: string): bigint {
switch (typeof(value)) {
case "bigint": return value;
case "number":
if (!Number.isInteger(value)) {
this.throwArgumentError("underflow", name || "value", value);
} else if (value < -maxValue || value > maxValue) {
this.throwArgumentError("overflow", name || "value", value);
}
return BigInt(value);
case "string":
try {
return BigInt(value);
} catch(e: any) {
this.throwArgumentError(`invalid BigNumberish string: ${ e.message }`, name || "value", value);
}
}
return this.throwArgumentError("invalid BigNumberish value", name || "value", value);
}
#log(_logLevel: LogLevel, args: Array<any>): void { #log(_logLevel: LogLevel, args: Array<any>): void {
const logLevel = LogLevels.indexOf(_logLevel); const logLevel = LogLevels.indexOf(_logLevel);
if (logLevel === -1) { if (logLevel === -1) {
this.throwArgumentError("invalid log level name", "logLevel", _logLevel); throwArgumentError("invalid log level name", "logLevel", _logLevel);
} }
if (this.#logLevel > logLevel) { return; } if (this.#logLevel > logLevel) { return; }
console.log.apply(console, args); console.log.apply(console, args);
@ -248,7 +63,5 @@ export class Logger {
export const logger = new Logger(version); export const logger = new Logger(version);
export function assertArgument(check: unknown, message: string, name: string, value: unknown): asserts check {
if (!check) { logger.throwArgumentError(message, name, value); }
}
*/

@ -1,5 +1,5 @@
import { hexlify, isBytesLike } from "./data.js"; import { hexlify, isBytesLike } from "./data.js";
import { logger } from "./logger.js"; import { throwArgumentError } from "./errors.js";
import type { BytesLike } from "./data.js"; import type { BytesLike } from "./data.js";
@ -11,12 +11,15 @@ export type BigNumberish = string | Numeric;
const BN_0 = BigInt(0); const BN_0 = BigInt(0);
const BN_1 = BigInt(1); const BN_1 = BigInt(1);
// IEEE 754 support 53-bits of mantissa
const maxValue = 0x1fffffffffffff;
/** /**
* Convert %%value%% from a twos-compliment value of %%width%% bits. * Convert %%value%% from a twos-compliment value of %%width%% bits.
*/ */
export function fromTwos(_value: BigNumberish, _width: Numeric): bigint { export function fromTwos(_value: BigNumberish, _width: Numeric): bigint {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
const width = BigInt(logger.getNumber(_width, "width")); const width = BigInt(getNumber(_width, "width"));
// Top bit set; treat as a negative value // Top bit set; treat as a negative value
if (value >> (width - BN_1)) { if (value >> (width - BN_1)) {
@ -31,8 +34,8 @@ export function fromTwos(_value: BigNumberish, _width: Numeric): bigint {
* Convert %%value%% to a twos-compliment value of %%width%% bits. * Convert %%value%% to a twos-compliment value of %%width%% bits.
*/ */
export function toTwos(_value: BigNumberish, _width: Numeric): bigint { export function toTwos(_value: BigNumberish, _width: Numeric): bigint {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
const width = BigInt(logger.getNumber(_width, "width")); const width = BigInt(getNumber(_width, "width"));
if (value < BN_0) { if (value < BN_0) {
const mask = (BN_1 << width) - BN_1; const mask = (BN_1 << width) - BN_1;
@ -46,11 +49,30 @@ export function toTwos(_value: BigNumberish, _width: Numeric): bigint {
* Mask %%value%% with a bitmask of %%bits%% ones. * Mask %%value%% with a bitmask of %%bits%% ones.
*/ */
export function mask(_value: BigNumberish, _bits: Numeric): bigint { export function mask(_value: BigNumberish, _bits: Numeric): bigint {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
const bits = BigInt(logger.getNumber(_bits, "bits")); const bits = BigInt(getNumber(_bits, "bits"));
return value & ((BN_1 << bits) - BN_1); return value & ((BN_1 << bits) - BN_1);
} }
export function getBigInt(value: BigNumberish, name?: string): bigint {
switch (typeof(value)) {
case "bigint": return value;
case "number":
if (!Number.isInteger(value)) {
throwArgumentError("underflow", name || "value", value);
} else if (value < -maxValue || value > maxValue) {
throwArgumentError("overflow", name || "value", value);
}
return BigInt(value);
case "string":
try {
return BigInt(value);
} catch(e: any) {
throwArgumentError(`invalid BigNumberish string: ${ e.message }`, name || "value", value);
}
}
return throwArgumentError("invalid BigNumberish value", name || "value", value);
}
/* /*
@ -68,15 +90,40 @@ export function toBigInt(value: BigNumberish | Uint8Array): bigint {
return BigInt(result); return BigInt(result);
} }
return logger.getBigInt(value); return getBigInt(value);
} }
export function getNumber(value: BigNumberish, name?: string): number {
switch (typeof(value)) {
case "bigint":
if (value < -maxValue || value > maxValue) {
throwArgumentError("overflow", name || "value", value);
}
return Number(value);
case "number":
if (!Number.isInteger(value)) {
throwArgumentError("underflow", name || "value", value);
} else if (value < -maxValue || value > maxValue) {
throwArgumentError("overflow", name || "value", value);
}
return value;
case "string":
try {
return getNumber(BigInt(value), name);
} catch(e: any) {
throwArgumentError(`invalid numeric string: ${ e.message }`, name || "value", value);
}
}
return throwArgumentError("invalid numeric value", name || "value", value);
}
/* /*
* Converts %%value%% to a number. If %%value%% is a Uint8Array, it * Converts %%value%% to a number. If %%value%% is a Uint8Array, it
* is treated as Big Endian data. Throws if the value is not safe. * is treated as Big Endian data. Throws if the value is not safe.
*/ */
export function toNumber(value: BigNumberish | Uint8Array): number { export function toNumber(value: BigNumberish | Uint8Array): number {
return logger.getNumber(toBigInt(value)); return getNumber(toBigInt(value));
} }
/** /**
@ -85,7 +132,7 @@ export function toNumber(value: BigNumberish | Uint8Array): number {
*/ */
// Converts value to hex, optionally padding on the left to width bytes // Converts value to hex, optionally padding on the left to width bytes
export function toHex(_value: BigNumberish, _width?: Numeric): string { export function toHex(_value: BigNumberish, _width?: Numeric): string {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
if (value < 0) { throw new Error("cannot convert negative value to hex"); } if (value < 0) { throw new Error("cannot convert negative value to hex"); }
let result = value.toString(16); let result = value.toString(16);
@ -94,7 +141,7 @@ export function toHex(_value: BigNumberish, _width?: Numeric): string {
// Ensure the value is of even length // Ensure the value is of even length
if (result.length % 2) { result = "0" + result; } if (result.length % 2) { result = "0" + result; }
} else { } else {
const width = logger.getNumber(_width, "width"); const width = getNumber(_width, "width");
if (width * 2 < result.length) { throw new Error(`value ${ value } exceeds width ${ width }`); } if (width * 2 < result.length) { throw new Error(`value ${ value } exceeds width ${ width }`); }
// Pad the value to the required width // Pad the value to the required width
@ -109,7 +156,7 @@ export function toHex(_value: BigNumberish, _width?: Numeric): string {
* Converts %%value%% to a Big Endian Uint8Array. * Converts %%value%% to a Big Endian Uint8Array.
*/ */
export function toArray(_value: BigNumberish): Uint8Array { export function toArray(_value: BigNumberish): Uint8Array {
const value = logger.getBigInt(_value, "value"); const value = getBigInt(_value, "value");
if (value < 0) { throw new Error("cannot convert negative value to hex"); } if (value < 0) { throw new Error("cannot convert negative value to hex"); }
if (value === BN_0) { return new Uint8Array([ ]); } if (value === BN_0) { return new Uint8Array([ ]); }

@ -1,7 +1,8 @@
//See: https://github.com/ethereum/wiki/wiki/RLP //See: https://github.com/ethereum/wiki/wiki/RLP
import { hexlify } from "./data.js"; import { hexlify } from "./data.js";
import { logger } from "./logger.js"; import { throwArgumentError, throwError } from "./errors.js";
import { getBytes } from "./data.js";
import type { BytesLike, RlpStructuredData } from "./index.js"; import type { BytesLike, RlpStructuredData } from "./index.js";
@ -35,7 +36,7 @@ function _decodeChildren(data: Uint8Array, offset: number, childOffset: number,
childOffset += decoded.consumed; childOffset += decoded.consumed;
if (childOffset > offset + 1 + length) { if (childOffset > offset + 1 + length) {
logger.throwError("child data too short", "BUFFER_OVERRUN", { throwError("child data too short", "BUFFER_OVERRUN", {
buffer: data, length, offset buffer: data, length, offset
}); });
} }
@ -47,14 +48,14 @@ function _decodeChildren(data: Uint8Array, offset: number, childOffset: number,
// returns { consumed: number, result: Object } // returns { consumed: number, result: Object }
function _decode(data: Uint8Array, offset: number): { consumed: number, result: any } { function _decode(data: Uint8Array, offset: number): { consumed: number, result: any } {
if (data.length === 0) { if (data.length === 0) {
logger.throwError("data too short", "BUFFER_OVERRUN", { throwError("data too short", "BUFFER_OVERRUN", {
buffer: data, length: 0, offset: 1 buffer: data, length: 0, offset: 1
}); });
} }
const checkOffset = (offset: number) => { const checkOffset = (offset: number) => {
if (offset > data.length) { if (offset > data.length) {
logger.throwError("data short segment too short", "BUFFER_OVERRUN", { throwError("data short segment too short", "BUFFER_OVERRUN", {
buffer: data, length: data.length, offset buffer: data, length: data.length, offset
}); });
} }
@ -98,10 +99,10 @@ function _decode(data: Uint8Array, offset: number): { consumed: number, result:
} }
export function decodeRlp(_data: BytesLike): RlpStructuredData { export function decodeRlp(_data: BytesLike): RlpStructuredData {
const data = logger.getBytes(_data, "data"); const data = getBytes(_data, "data");
const decoded = _decode(data, 0); const decoded = _decode(data, 0);
if (decoded.consumed !== data.length) { if (decoded.consumed !== data.length) {
logger.throwArgumentError("unexpected junk after rlp payload", "data", _data); throwArgumentError("unexpected junk after rlp payload", "data", _data);
} }
return decoded.result; return decoded.result;
} }

@ -1,6 +1,6 @@
//See: https://github.com/ethereum/wiki/wiki/RLP //See: https://github.com/ethereum/wiki/wiki/RLP
import { logger } from "./logger.js"; import { getBytes } from "./data.js";
import type { RlpStructuredData } from "./rlp.js"; import type { RlpStructuredData } from "./rlp.js";
@ -33,7 +33,7 @@ function _encode(object: Array<any> | string): Array<number> {
} }
const data: Array<number> = Array.prototype.slice.call(logger.getBytes(object, "object")); const data: Array<number> = Array.prototype.slice.call(getBytes(object, "object"));
if (data.length === 1 && data[0] <= 0x7f) { if (data.length === 1 && data[0] <= 0x7f) {
return data; return data;

@ -1,5 +1,5 @@
import { formatFixed, parseFixed } from "./fixednumber.js"; import { formatFixed, parseFixed } from "./fixednumber.js";
import { logger } from "./logger.js"; import { throwArgumentError } from "./errors.js";
import type { BigNumberish, Numeric } from "../utils/index.js"; import type { BigNumberish, Numeric } from "../utils/index.js";
@ -23,7 +23,7 @@ const names = [
export function formatUnits(value: BigNumberish, unit?: string | Numeric): string { export function formatUnits(value: BigNumberish, unit?: string | Numeric): string {
if (typeof(unit) === "string") { if (typeof(unit) === "string") {
const index = names.indexOf(unit); const index = names.indexOf(unit);
if (index === -1) { logger.throwArgumentError("invalid unit", "unit", unit); } if (index === -1) { throwArgumentError("invalid unit", "unit", unit); }
unit = 3 * index; unit = 3 * index;
} }
return formatFixed(value, (unit != null) ? unit: 18); return formatFixed(value, (unit != null) ? unit: 18);
@ -36,12 +36,12 @@ export function formatUnits(value: BigNumberish, unit?: string | Numeric): strin
*/ */
export function parseUnits(value: string, unit?: string | Numeric): bigint { export function parseUnits(value: string, unit?: string | Numeric): bigint {
if (typeof(value) !== "string") { if (typeof(value) !== "string") {
logger.throwArgumentError("value must be a string", "value", value); throwArgumentError("value must be a string", "value", value);
} }
if (typeof(unit) === "string") { if (typeof(unit) === "string") {
const index = names.indexOf(unit); const index = names.indexOf(unit);
if (index === -1) { logger.throwArgumentError("invalid unit", "unit", unit); } if (index === -1) { throwArgumentError("invalid unit", "unit", unit); }
unit = 3 * index; unit = 3 * index;
} }
return parseFixed(value, (unit != null) ? unit: 18); return parseFixed(value, (unit != null) ? unit: 18);

@ -1,4 +1,5 @@
import { logger } from "./logger.js"; import { getBytes } from "./data.js";
import { assertNormalize, throwArgumentError } from "./errors.js";
import type { BytesLike } from "./index.js"; import type { BytesLike } from "./index.js";
@ -42,8 +43,11 @@ export type Utf8ErrorReason =
export type Utf8ErrorFunc = (reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number) => number; export type Utf8ErrorFunc = (reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number) => number;
function errorFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number { function errorFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number {
return logger.throwArgumentError(`invalid codepoint at offset ${ offset }; ${ reason }`, "bytes", bytes); return throwArgumentError(`invalid codepoint at offset ${ offset }; ${ reason }`, "bytes", bytes);
} }
function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number { function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number {
@ -94,7 +98,7 @@ export const Utf8ErrorFuncs: Readonly<Record<"error" | "ignore" | "replace", Utf
function getUtf8CodePoints(_bytes: BytesLike, onError?: Utf8ErrorFunc): Array<number> { function getUtf8CodePoints(_bytes: BytesLike, onError?: Utf8ErrorFunc): Array<number> {
if (onError == null) { onError = Utf8ErrorFuncs.error; } if (onError == null) { onError = Utf8ErrorFuncs.error; }
const bytes = logger.getBytes(_bytes, "bytes"); const bytes = getBytes(_bytes, "bytes");
const result: Array<number> = []; const result: Array<number> = [];
let i = 0; let i = 0;
@ -192,7 +196,7 @@ function getUtf8CodePoints(_bytes: BytesLike, onError?: Utf8ErrorFunc): Array<nu
export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8Array { export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8Array {
if (form != null) { if (form != null) {
logger.assertNormalize(form); assertNormalize(form);
str = str.normalize(form); str = str.normalize(form);
} }
@ -232,7 +236,7 @@ export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8
return new Uint8Array(result); return new Uint8Array(result);
}; };
function escapeChar(value: number) { function escapeChar(value: number): string {
const hex = ("0000" + value.toString(16)); const hex = ("0000" + value.toString(16));
return "\\u" + hex.substring(hex.length - 4); return "\\u" + hex.substring(hex.length - 4);
} }
@ -283,3 +287,4 @@ export function toUtf8String(bytes: BytesLike, onError?: Utf8ErrorFunc): string
export function toUtf8CodePoints(str: string, form?: UnicodeNormalizationForm): Array<number> { export function toUtf8CodePoints(str: string, form?: UnicodeNormalizationForm): Array<number> {
return getUtf8CodePoints(toUtf8Bytes(str, form)); return getUtf8CodePoints(toUtf8Bytes(str, form));
} }

@ -2,7 +2,9 @@ import { getAddress, resolveAddress } from "../address/index.js";
import { hashMessage, TypedDataEncoder } from "../hash/index.js"; import { hashMessage, TypedDataEncoder } from "../hash/index.js";
import { AbstractSigner } from "../providers/index.js"; import { AbstractSigner } from "../providers/index.js";
import { computeAddress, Transaction } from "../transaction/index.js"; import { computeAddress, Transaction } from "../transaction/index.js";
import { defineProperties, logger, resolveProperties } from "../utils/index.js"; import {
defineProperties, resolveProperties, throwArgumentError, throwError
} from "../utils/index.js";
import type { SigningKey } from "../crypto/index.js"; import type { SigningKey } from "../crypto/index.js";
import type { TypedDataDomain, TypedDataField } from "../hash/index.js"; import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
@ -42,7 +44,7 @@ export class BaseWallet extends AbstractSigner {
if (tx.from != null) { if (tx.from != null) {
if (getAddress(tx.from) !== this.address) { if (getAddress(tx.from) !== this.address) {
logger.throwArgumentError("transaction from address mismatch", "tx.from", _tx.from); throwArgumentError("transaction from address mismatch", "tx.from", _tx.from);
} }
delete tx.from; delete tx.from;
} }
@ -63,7 +65,7 @@ export class BaseWallet extends AbstractSigner {
// Populate any ENS names // Populate any ENS names
const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => { const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => {
if (this.provider == null) { if (this.provider == null) {
return logger.throwError("cannot resolve ENS names without a provider", "UNSUPPORTED_OPERATION", { return throwError("cannot resolve ENS names without a provider", "UNSUPPORTED_OPERATION", {
operation: "resolveName", operation: "resolveName",
info: { name } info: { name }
}); });
@ -71,7 +73,7 @@ export class BaseWallet extends AbstractSigner {
const address = await this.provider.resolveName(name); const address = await this.provider.resolveName(name);
if (address == null) { if (address == null) {
return logger.throwError("unconfigured ENS name", "UNCONFIGURED_NAME", { return throwError("unconfigured ENS name", "UNCONFIGURED_NAME", {
value: name value: name
}); });
} }

@ -2,7 +2,10 @@ import { computeHmac, randomBytes, ripemd160, SigningKey, sha256 } from "../cryp
import { VoidSigner } from "../providers/index.js"; import { VoidSigner } from "../providers/index.js";
import { computeAddress } from "../transaction/index.js"; import { computeAddress } from "../transaction/index.js";
import { import {
concat, dataSlice, decodeBase58, defineProperties, encodeBase58, hexlify, logger, toBigInt, toHex concat, dataSlice, decodeBase58, defineProperties, encodeBase58,
getBytes, hexlify,
getNumber, toBigInt, toHex,
assertPrivate, throwArgumentError, throwError
} from "../utils/index.js"; } from "../utils/index.js";
import { langEn } from "../wordlists/lang-en.js"; import { langEn } from "../wordlists/lang-en.js";
@ -36,7 +39,7 @@ function zpad(value: number, length: number): string {
} }
function encodeBase58Check(_value: BytesLike): string { function encodeBase58Check(_value: BytesLike): string {
const value = logger.getBytes(_value); const value = getBytes(_value);
const check = dataSlice(sha256(sha256(value)), 0, 4); const check = dataSlice(sha256(sha256(value)), 0, 4);
const bytes = concat([ value, check ]); const bytes = concat([ value, check ]);
return encodeBase58(bytes); return encodeBase58(bytes);
@ -49,22 +52,22 @@ function ser_I(index: number, chainCode: string, publicKey: string, privateKey:
if (index & HardenedBit) { if (index & HardenedBit) {
if (privateKey == null) { if (privateKey == null) {
return logger.throwError("cannot derive child of neutered node", "UNSUPPORTED_OPERATION", { return throwError("cannot derive child of neutered node", "UNSUPPORTED_OPERATION", {
operation: "deriveChild" operation: "deriveChild"
}); });
} }
// Data = 0x00 || ser_256(k_par) // Data = 0x00 || ser_256(k_par)
data.set(logger.getBytes(privateKey), 1); data.set(getBytes(privateKey), 1);
} else { } else {
// Data = ser_p(point(k_par)) // Data = ser_p(point(k_par))
data.set(logger.getBytes(publicKey)); data.set(getBytes(publicKey));
} }
// Data += ser_32(i) // Data += ser_32(i)
for (let i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff); } for (let i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff); }
const I = logger.getBytes(computeHmac("sha512", chainCode, data)); const I = getBytes(computeHmac("sha512", chainCode, data));
return { IL: I.slice(0, 32), IR: I.slice(32) }; return { IL: I.slice(0, 32), IR: I.slice(32) };
} }
@ -122,7 +125,7 @@ export class HDNodeWallet extends BaseWallet {
constructor(guard: any, signingKey: SigningKey, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, mnemonic: null | Mnemonic, provider: null | Provider) { constructor(guard: any, signingKey: SigningKey, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, mnemonic: null | Mnemonic, provider: null | Provider) {
super(signingKey, provider); super(signingKey, provider);
logger.assertPrivate(guard, _guard, "HDNodeWallet"); assertPrivate(guard, _guard, "HDNodeWallet");
defineProperties<HDNodeWallet>(this, { publicKey: signingKey.compressedPublicKey }); defineProperties<HDNodeWallet>(this, { publicKey: signingKey.compressedPublicKey });
@ -165,7 +168,7 @@ export class HDNodeWallet extends BaseWallet {
} }
deriveChild(_index: Numeric): HDNodeWallet { deriveChild(_index: Numeric): HDNodeWallet {
const index = logger.getNumber(_index, "index"); const index = getNumber(_index, "index");
if (index > 0xffffffff) { throw new Error("invalid index - " + String(index)); } if (index > 0xffffffff) { throw new Error("invalid index - " + String(index)); }
// Base path // Base path
@ -188,12 +191,12 @@ export class HDNodeWallet extends BaseWallet {
} }
static #fromSeed(_seed: BytesLike, mnemonic: null | Mnemonic): HDNodeWallet { static #fromSeed(_seed: BytesLike, mnemonic: null | Mnemonic): HDNodeWallet {
const seed = logger.getBytes(_seed, "seed"); const seed = getBytes(_seed, "seed");
if (seed.length < 16 || seed.length > 64) { if (seed.length < 16 || seed.length > 64) {
throw new Error("invalid seed"); throw new Error("invalid seed");
} }
const I = logger.getBytes(computeHmac("sha512", MasterSecret, seed)); const I = getBytes(computeHmac("sha512", MasterSecret, seed));
const signingKey = new SigningKey(hexlify(I.slice(0, 32))); const signingKey = new SigningKey(hexlify(I.slice(0, 32)));
return new HDNodeWallet(_guard, signingKey, "0x00000000", hexlify(I.slice(32)), return new HDNodeWallet(_guard, signingKey, "0x00000000", hexlify(I.slice(32)),
@ -204,7 +207,7 @@ export class HDNodeWallet extends BaseWallet {
return HDNodeWallet.#fromSeed(seed, null); return HDNodeWallet.#fromSeed(seed, null);
} }
static fromPhrase(phrase: string, password = "", path: null | string = defaultPath, wordlist: Wordlist = langEn): HDNodeWallet { static fromPhrase(phrase: string, password: string = "", path: null | string = defaultPath, wordlist: Wordlist = langEn): HDNodeWallet {
if (!path) { path = defaultPath; } if (!path) { path = defaultPath; }
const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist) const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist)
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path); return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
@ -216,10 +219,10 @@ export class HDNodeWallet extends BaseWallet {
} }
static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet { static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet {
const bytes = logger.getBytes(decodeBase58(extendedKey)); // @TODO: redact const bytes = getBytes(decodeBase58(extendedKey)); // @TODO: redact
if (bytes.length !== 82 || encodeBase58Check(bytes.slice(0, 78)) !== extendedKey) { if (bytes.length !== 82 || encodeBase58Check(bytes.slice(0, 78)) !== extendedKey) {
logger.throwArgumentError("invalid extended key", "extendedKey", "[ REDACTED ]"); throwArgumentError("invalid extended key", "extendedKey", "[ REDACTED ]");
} }
const depth = bytes[4]; const depth = bytes[4];
@ -244,10 +247,10 @@ export class HDNodeWallet extends BaseWallet {
} }
return logger.throwArgumentError("invalid extended key prefix", "extendedKey", "[ REDACTED ]"); return throwArgumentError("invalid extended key prefix", "extendedKey", "[ REDACTED ]");
} }
static createRandom(password = "", path: null | string = defaultPath, wordlist: Wordlist = langEn): HDNodeWallet { static createRandom(password: string = "", path: null | string = defaultPath, wordlist: Wordlist = langEn): HDNodeWallet {
if (!path) { path = defaultPath; } if (!path) { path = defaultPath; }
const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist) const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist)
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path); return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
@ -268,7 +271,7 @@ export class HDNodeVoidWallet extends VoidSigner {
constructor(guard: any, address: string, publicKey: string, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, provider: null | Provider) { constructor(guard: any, address: string, publicKey: string, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, provider: null | Provider) {
super(address, provider); super(address, provider);
logger.assertPrivate(guard, _guard, "HDNodeVoidWallet"); assertPrivate(guard, _guard, "HDNodeVoidWallet");
defineProperties<HDNodeVoidWallet>(this, { publicKey }); defineProperties<HDNodeVoidWallet>(this, { publicKey });
@ -305,7 +308,7 @@ export class HDNodeVoidWallet extends VoidSigner {
hasPath(): this is { path: string } { return (this.path != null); } hasPath(): this is { path: string } { return (this.path != null); }
deriveChild(_index: Numeric): HDNodeVoidWallet { deriveChild(_index: Numeric): HDNodeVoidWallet {
const index = logger.getNumber(_index, "index"); const index = getNumber(_index, "index");
if (index > 0xffffffff) { throw new Error("invalid index - " + String(index)); } if (index > 0xffffffff) { throw new Error("invalid index - " + String(index)); }
// Base path // Base path
@ -333,19 +336,19 @@ export class HDNodeVoidWallet extends VoidSigner {
export class HDNodeWalletManager { export class HDNodeWalletManager {
#root: HDNodeWallet; #root: HDNodeWallet;
constructor(phrase: string, password = "", path = "m/44'/60'/0'/0", locale: Wordlist = langEn) { constructor(phrase: string, password: string = "", path: string = "m/44'/60'/0'/0", locale: Wordlist = langEn) {
this.#root = HDNodeWallet.fromPhrase(phrase, password, path, locale); this.#root = HDNodeWallet.fromPhrase(phrase, password, path, locale);
} }
getSigner(index = 0): HDNodeWallet { getSigner(index?: number): HDNodeWallet {
return this.#root.deriveChild(index); return this.#root.deriveChild((index == null) ? 0: index);
} }
} }
export function getAccountPath(_index: Numeric): string { export function getAccountPath(_index: Numeric): string {
const index = logger.getNumber(_index, "index"); const index = getNumber(_index, "index");
if (index < 0 || index >= HardenedBit) { if (index < 0 || index >= HardenedBit) {
logger.throwArgumentError("invalid account index", "index", index); throwArgumentError("invalid account index", "index", index);
} }
return `m/44'/60'/${ index }'/0/0`; return `m/44'/60'/${ index }'/0/0`;
} }

@ -3,7 +3,7 @@ import { CBC, pkcs7Strip } from "aes-js";
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { pbkdf2 } from "../crypto/index.js"; import { pbkdf2 } from "../crypto/index.js";
import { id } from "../hash/id.js"; import { id } from "../hash/id.js";
import { logger } from "../utils/index.js"; import { getBytes, throwArgumentError } from "../utils/index.js";
import { getPassword, looseArrayify, spelunk } from "./utils.js"; import { getPassword, looseArrayify, spelunk } from "./utils.js";
@ -32,17 +32,17 @@ export function decryptCrowdsaleJson(json: string, _password: string | Uint8Arra
// Encrypted Seed // Encrypted Seed
const encseed = looseArrayify(spelunk(data, "encseed:string!")); const encseed = looseArrayify(spelunk(data, "encseed:string!"));
if (!encseed || (encseed.length % 16) !== 0) { if (!encseed || (encseed.length % 16) !== 0) {
logger.throwArgumentError("invalid encseed", "json", json); throwArgumentError("invalid encseed", "json", json);
} }
const key = logger.getBytes(pbkdf2(password, password, 2000, 32, "sha256")).slice(0, 16); const key = getBytes(pbkdf2(password, password, 2000, 32, "sha256")).slice(0, 16);
const iv = encseed.slice(0, 16); const iv = encseed.slice(0, 16);
const encryptedSeed = encseed.slice(16); const encryptedSeed = encseed.slice(16);
// Decrypt the seed // Decrypt the seed
const aesCbc = new CBC(key, iv); const aesCbc = new CBC(key, iv);
const seed = pkcs7Strip(logger.getBytes(aesCbc.decrypt(encryptedSeed))); const seed = pkcs7Strip(getBytes(aesCbc.decrypt(encryptedSeed)));
// This wallet format is weird... Convert the binary encoded hex to a string. // This wallet format is weird... Convert the binary encoded hex to a string.
let seedHex = ""; let seedHex = "";

@ -3,7 +3,9 @@ import { CTR } from "aes-js";
import { getAddress } from "../address/index.js"; import { getAddress } from "../address/index.js";
import { keccak256, pbkdf2, randomBytes, scrypt, scryptSync } from "../crypto/index.js"; import { keccak256, pbkdf2, randomBytes, scrypt, scryptSync } from "../crypto/index.js";
import { computeAddress } from "../transaction/index.js"; import { computeAddress } from "../transaction/index.js";
import { concat, hexlify, logger } from "../utils/index.js"; import {
concat, getBytes, hexlify, throwArgumentError, throwError
} from "../utils/index.js";
import { getPassword, spelunk, uuidV4, zpad } from "./utils.js"; import { getPassword, spelunk, uuidV4, zpad } from "./utils.js";
@ -65,18 +67,18 @@ function decrypt(data: any, key: Uint8Array, ciphertext: Uint8Array): string {
return hexlify(aesCtr.decrypt(ciphertext)); return hexlify(aesCtr.decrypt(ciphertext));
} }
return logger.throwError("unsupported cipher", "UNSUPPORTED_OPERATION", { return throwError("unsupported cipher", "UNSUPPORTED_OPERATION", {
operation: "decrypt" operation: "decrypt"
}); });
} }
function getAccount(data: any, _key: string): KeystoreAccount { function getAccount(data: any, _key: string): KeystoreAccount {
const key = logger.getBytes(_key); const key = getBytes(_key);
const ciphertext = spelunk<Uint8Array>(data, "crypto.ciphertext:data!"); const ciphertext = spelunk<Uint8Array>(data, "crypto.ciphertext:data!");
const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2); const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2);
if (computedMAC !== spelunk(data, "crypto.mac:string!").toLowerCase()) { if (computedMAC !== spelunk(data, "crypto.mac:string!").toLowerCase()) {
return logger.throwArgumentError("incorrect password", "password", "[ REDACTED ]"); return throwArgumentError("incorrect password", "password", "[ REDACTED ]");
} }
const privateKey = decrypt(data, key.slice(0, 16), ciphertext); const privateKey = decrypt(data, key.slice(0, 16), ciphertext);
@ -87,7 +89,7 @@ function getAccount(data: any, _key: string): KeystoreAccount {
if (check.substring(0, 2) !== "0x") { check = "0x" + check; } if (check.substring(0, 2) !== "0x") { check = "0x" + check; }
if (getAddress(check) !== address) { if (getAddress(check) !== address) {
logger.throwArgumentError("keystore address/privateKey mismatch", "address", data.address); throwArgumentError("keystore address/privateKey mismatch", "address", data.address);
} }
} }
@ -106,7 +108,7 @@ function getAccount(data: any, _key: string): KeystoreAccount {
account.mnemonic = { account.mnemonic = {
path: (spelunk<null | string>(data, "x-ethers.path:string") || defaultPath), path: (spelunk<null | string>(data, "x-ethers.path:string") || defaultPath),
locale: (spelunk<null | string>(data, "x-ethers.locale:string") || "en"), locale: (spelunk<null | string>(data, "x-ethers.locale:string") || "en"),
entropy: hexlify(logger.getBytes(mnemonicAesCtr.decrypt(mnemonicCiphertext))) entropy: hexlify(getBytes(mnemonicAesCtr.decrypt(mnemonicCiphertext)))
}; };
} }
@ -132,7 +134,7 @@ function getKdfParams<T>(data: any): KdfParams {
const kdf = spelunk(data, "crypto.kdf:string"); const kdf = spelunk(data, "crypto.kdf:string");
if (kdf && typeof(kdf) === "string") { if (kdf && typeof(kdf) === "string") {
const throwError = function(name: string, value: any): never { const throwError = function(name: string, value: any): never {
return logger.throwArgumentError("invalid key-derivation function parameters", name, value); return throwArgumentError("invalid key-derivation function parameters", name, value);
} }
if (kdf.toLowerCase() === "scrypt") { if (kdf.toLowerCase() === "scrypt") {
@ -171,7 +173,7 @@ function getKdfParams<T>(data: any): KdfParams {
} }
} }
return logger.throwArgumentError("unsupported key-derivation function", "kdf", kdf); return throwArgumentError("unsupported key-derivation function", "kdf", kdf);
} }
@ -252,7 +254,7 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st
} }
if (!options) { options = {}; } if (!options) { options = {}; }
const privateKey = logger.getBytes(account.privateKey, "privateKey"); const privateKey = getBytes(account.privateKey, "privateKey");
const passwordBytes = getPassword(password); const passwordBytes = getPassword(password);
/* /*
@ -269,18 +271,18 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st
} }
*/ */
// Check/generate the salt // Check/generate the salt
const salt = (options.salt != null) ? logger.getBytes(options.salt, "options.slat"): randomBytes(32); const salt = (options.salt != null) ? getBytes(options.salt, "options.slat"): randomBytes(32);
// Override initialization vector // Override initialization vector
const iv = (options.iv != null) ? logger.getBytes(options.iv, "options.iv"): randomBytes(16); const iv = (options.iv != null) ? getBytes(options.iv, "options.iv"): randomBytes(16);
if (iv.length !== 16) { if (iv.length !== 16) {
logger.throwArgumentError("invalid options.iv", "options.iv", options.iv); throwArgumentError("invalid options.iv", "options.iv", options.iv);
} }
// Override the uuid // Override the uuid
const uuidRandom = (options.uuid != null) ? logger.getBytes(options.uuid, "options.uuid"): randomBytes(16); const uuidRandom = (options.uuid != null) ? getBytes(options.uuid, "options.uuid"): randomBytes(16);
if (uuidRandom.length !== 16) { if (uuidRandom.length !== 16) {
logger.throwArgumentError("invalid options.uuid", "options.uuid", options.iv); throwArgumentError("invalid options.uuid", "options.uuid", options.iv);
} }
if (uuidRandom.length !== 16) { throw new Error("invalid uuid"); } if (uuidRandom.length !== 16) { throw new Error("invalid uuid"); }
@ -296,7 +298,7 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix) // - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
// - 32 bytes AES key to encrypt mnemonic with (required here to be Ethers Wallet) // - 32 bytes AES key to encrypt mnemonic with (required here to be Ethers Wallet)
const _key = await scrypt(passwordBytes, salt, N, r, p, 64, progressCallback); const _key = await scrypt(passwordBytes, salt, N, r, p, 64, progressCallback);
const key = logger.getBytes(_key); const key = getBytes(_key);
// This will be used to encrypt the wallet (as per Web3 secret storage) // This will be used to encrypt the wallet (as per Web3 secret storage)
const derivedKey = key.slice(0, 16); const derivedKey = key.slice(0, 16);
@ -304,7 +306,7 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st
// Encrypt the private key // Encrypt the private key
const aesCtr = new CTR(derivedKey, iv); const aesCtr = new CTR(derivedKey, iv);
const ciphertext = logger.getBytes(aesCtr.encrypt(privateKey)); const ciphertext = getBytes(aesCtr.encrypt(privateKey));
// Compute the message authentication code, used to check the password // Compute the message authentication code, used to check the password
const mac = keccak256(concat([ macPrefix, ciphertext ])) const mac = keccak256(concat([ macPrefix, ciphertext ]))
@ -341,10 +343,10 @@ export async function encryptKeystoreJson(account: KeystoreAccount, password: st
const mnemonicKey = key.slice(32, 64); const mnemonicKey = key.slice(32, 64);
const entropy = logger.getBytes(account.mnemonic.entropy, "account.mnemonic.entropy"); const entropy = getBytes(account.mnemonic.entropy, "account.mnemonic.entropy");
const mnemonicIv = randomBytes(16); const mnemonicIv = randomBytes(16);
const mnemonicAesCtr = new CTR(mnemonicKey, mnemonicIv); const mnemonicAesCtr = new CTR(mnemonicKey, mnemonicIv);
const mnemonicCiphertext = logger.getBytes(mnemonicAesCtr.encrypt(entropy)); const mnemonicCiphertext = getBytes(mnemonicAesCtr.encrypt(entropy));
const now = new Date(); const now = new Date();
const timestamp = (now.getUTCFullYear() + "-" + const timestamp = (now.getUTCFullYear() + "-" +

@ -1,5 +1,7 @@
import { pbkdf2, sha256 } from "../crypto/index.js"; import { pbkdf2, sha256 } from "../crypto/index.js";
import { defineProperties, hexlify, logger, toUtf8Bytes } from "../utils/index.js"; import {
defineProperties, getBytes, hexlify, assertNormalize, assertPrivate, throwArgumentError, toUtf8Bytes
} from "../utils/index.js";
import { langEn } from "../wordlists/lang-en.js"; import { langEn } from "../wordlists/lang-en.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -18,13 +20,13 @@ function getLowerMask(bits: number): number {
function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn): string { function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn): string {
logger.assertNormalize("NFKD"); assertNormalize("NFKD");
if (wordlist == null) { wordlist = langEn; } if (wordlist == null) { wordlist = langEn; }
const words = wordlist.split(mnemonic); const words = wordlist.split(mnemonic);
if ((words.length % 3) !== 0 || words.length < 12 || words.length > 24) { if ((words.length % 3) !== 0 || words.length < 12 || words.length > 24) {
logger.throwArgumentError("invalid mnemonic length", "mnemonic", "[ REDACTED ]"); throwArgumentError("invalid mnemonic length", "mnemonic", "[ REDACTED ]");
} }
const entropy = new Uint8Array(Math.ceil(11 * words.length / 8)); const entropy = new Uint8Array(Math.ceil(11 * words.length / 8));
@ -33,7 +35,7 @@ function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn)
for (let i = 0; i < words.length; i++) { for (let i = 0; i < words.length; i++) {
let index = wordlist.getWordIndex(words[i].normalize("NFKD")); let index = wordlist.getWordIndex(words[i].normalize("NFKD"));
if (index === -1) { if (index === -1) {
logger.throwArgumentError(`invalid mnemonic word at index ${ i }`, "mnemonic", "[ REDACTED ]"); throwArgumentError(`invalid mnemonic word at index ${ i }`, "mnemonic", "[ REDACTED ]");
} }
for (let bit = 0; bit < 11; bit++) { for (let bit = 0; bit < 11; bit++) {
@ -50,10 +52,10 @@ function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn)
const checksumBits = words.length / 3; const checksumBits = words.length / 3;
const checksumMask = getUpperMask(checksumBits); const checksumMask = getUpperMask(checksumBits);
const checksum = logger.getBytes(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask; const checksum = getBytes(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask;
if (checksum !== (entropy[entropy.length - 1] & checksumMask)) { if (checksum !== (entropy[entropy.length - 1] & checksumMask)) {
logger.throwArgumentError("invalid mnemonic checksum", "mnemonic", "[ REDACTED ]"); throwArgumentError("invalid mnemonic checksum", "mnemonic", "[ REDACTED ]");
} }
return hexlify(entropy.slice(0, entropyBits / 8)); return hexlify(entropy.slice(0, entropyBits / 8));
@ -61,7 +63,7 @@ function mnemonicToEntropy(mnemonic: string, wordlist: null | Wordlist = langEn)
function entropyToMnemonic(entropy: Uint8Array, wordlist: null | Wordlist = langEn): string { function entropyToMnemonic(entropy: Uint8Array, wordlist: null | Wordlist = langEn): string {
if ((entropy.length % 4) || entropy.length < 16 || entropy.length > 32) { if ((entropy.length % 4) || entropy.length < 16 || entropy.length > 32) {
logger.throwArgumentError("invalid entropy size", "entropy", "[ REDACTED ]"); throwArgumentError("invalid entropy size", "entropy", "[ REDACTED ]");
} }
if (wordlist == null) { wordlist = langEn; } if (wordlist == null) { wordlist = langEn; }
@ -113,7 +115,7 @@ export class Mnemonic {
constructor(guard: any, entropy: string, phrase: string, password?: null | string, wordlist?: null | Wordlist) { constructor(guard: any, entropy: string, phrase: string, password?: null | string, wordlist?: null | Wordlist) {
if (password == null) { password = ""; } if (password == null) { password = ""; }
if (wordlist == null) { wordlist = langEn; } if (wordlist == null) { wordlist = langEn; }
logger.assertPrivate(guard, _guard, "Mnemonic"); assertPrivate(guard, _guard, "Mnemonic");
defineProperties<Mnemonic>(this, { phrase, password, wordlist, entropy }); defineProperties<Mnemonic>(this, { phrase, password, wordlist, entropy });
} }
@ -122,21 +124,21 @@ export class Mnemonic {
return pbkdf2(toUtf8Bytes(this.phrase, "NFKD"), salt, 2048, 64, "sha512"); return pbkdf2(toUtf8Bytes(this.phrase, "NFKD"), salt, 2048, 64, "sha512");
} }
static fromPhrase(phrase: string, password?: null | string, wordlist?: null | Wordlist) { static fromPhrase(phrase: string, password?: null | string, wordlist?: null | Wordlist): Mnemonic {
// Normalize the case and space; throws if invalid // Normalize the case and space; throws if invalid
const entropy = mnemonicToEntropy(phrase, wordlist); const entropy = mnemonicToEntropy(phrase, wordlist);
phrase = entropyToMnemonic(logger.getBytes(entropy), wordlist); phrase = entropyToMnemonic(getBytes(entropy), wordlist);
return new Mnemonic(_guard, entropy, phrase, password, wordlist); return new Mnemonic(_guard, entropy, phrase, password, wordlist);
} }
static fromEntropy(_entropy: BytesLike, password?: null | string, wordlist?: null | Wordlist): Mnemonic { static fromEntropy(_entropy: BytesLike, password?: null | string, wordlist?: null | Wordlist): Mnemonic {
const entropy = logger.getBytes(_entropy, "entropy"); const entropy = getBytes(_entropy, "entropy");
const phrase = entropyToMnemonic(entropy, wordlist); const phrase = entropyToMnemonic(entropy, wordlist);
return new Mnemonic(_guard, hexlify(entropy), phrase, password, wordlist); return new Mnemonic(_guard, hexlify(entropy), phrase, password, wordlist);
} }
static entropyToPhrase(_entropy: BytesLike, wordlist?: null | Wordlist): string { static entropyToPhrase(_entropy: BytesLike, wordlist?: null | Wordlist): string {
const entropy = logger.getBytes(_entropy, "entropy"); const entropy = getBytes(_entropy, "entropy");
return entropyToMnemonic(entropy, wordlist); return entropyToMnemonic(entropy, wordlist);
} }

@ -1,4 +1,6 @@
import { hexlify, logger, toUtf8Bytes } from "../utils/index.js"; import {
getBytes, getBytesCopy, hexlify, throwArgumentError, toUtf8Bytes
} from "../utils/index.js";
import type { BytesLike } from "../utils/index.js"; import type { BytesLike } from "../utils/index.js";
@ -7,7 +9,7 @@ export function looseArrayify(hexString: string): Uint8Array {
if (typeof(hexString) === 'string' && hexString.substring(0, 2) !== '0x') { if (typeof(hexString) === 'string' && hexString.substring(0, 2) !== '0x') {
hexString = '0x' + hexString; hexString = '0x' + hexString;
} }
return logger.getBytesCopy(hexString); return getBytesCopy(hexString);
} }
export function zpad(value: String | number, length: number): String { export function zpad(value: String | number, length: number): String {
@ -20,14 +22,14 @@ export function getPassword(password: string | Uint8Array): Uint8Array {
if (typeof(password) === 'string') { if (typeof(password) === 'string') {
return toUtf8Bytes(password, "NFKC"); return toUtf8Bytes(password, "NFKC");
} }
return logger.getBytesCopy(password); return getBytesCopy(password);
} }
export function spelunk<T = string>(object: any, _path: string): T { export function spelunk<T = string>(object: any, _path: string): T {
const match = _path.match(/^([a-z0-9$_.-]*)(:([a-z]+))?(!)?$/i); const match = _path.match(/^([a-z0-9$_.-]*)(:([a-z]+))?(!)?$/i);
if (match == null) { if (match == null) {
return logger.throwArgumentError("invalid path", "path", _path); return throwArgumentError("invalid path", "path", _path);
} }
const path = match[1]; const path = match[1];
const type = match[3]; const type = match[3];
@ -59,7 +61,7 @@ export function spelunk<T = string>(object: any, _path: string): T {
} }
if (reqd && cur == null) { if (reqd && cur == null) {
logger.throwArgumentError("missing required value", "path", path); throwArgumentError("missing required value", "path", path);
} }
if (type && cur != null) { if (type && cur != null) {
@ -84,7 +86,7 @@ export function spelunk<T = string>(object: any, _path: string): T {
if (type === "array" && Array.isArray(cur)) { return <T><unknown>cur; } if (type === "array" && Array.isArray(cur)) { return <T><unknown>cur; }
if (type === typeof(cur)) { return cur; } if (type === typeof(cur)) { return cur; }
logger.throwArgumentError(`wrong type found for ${ type } `, "path", path); throwArgumentError(`wrong type found for ${ type } `, "path", path);
} }
return cur; return cur;
@ -122,7 +124,7 @@ export function followRequired(data: any, path: string): string {
*/ */
// See: https://www.ietf.org/rfc/rfc4122.txt (Section 4.4) // See: https://www.ietf.org/rfc/rfc4122.txt (Section 4.4)
export function uuidV4(randomBytes: BytesLike): string { export function uuidV4(randomBytes: BytesLike): string {
const bytes = logger.getBytes(randomBytes, "randomBytes"); const bytes = getBytes(randomBytes, "randomBytes");
// Section: 4.1.3: // Section: 4.1.3:
// - time_hi_and_version[12:16] = 0b0100 // - time_hi_and_version[12:16] = 0b0100

@ -1,6 +1,6 @@
import { randomBytes, SigningKey } from "../crypto/index.js"; import { randomBytes, SigningKey } from "../crypto/index.js";
import { computeAddress } from "../transaction/index.js"; import { computeAddress } from "../transaction/index.js";
import { isHexString, logger } from "../utils/index.js"; import { isHexString, throwArgumentError } from "../utils/index.js";
import { BaseWallet } from "./base-wallet.js"; import { BaseWallet } from "./base-wallet.js";
import { HDNodeWallet } from "./hdwallet.js"; import { HDNodeWallet } from "./hdwallet.js";
@ -90,7 +90,7 @@ export class Wallet extends BaseWallet {
if (signingKey == null) { signingKey = trySigningKey(key); } if (signingKey == null) { signingKey = trySigningKey(key); }
if (signingKey == null) { if (signingKey == null) {
logger.throwArgumentError("invalid key", "key", "[ REDACTED ]"); throwArgumentError("invalid key", "key", "[ REDACTED ]");
} }
super(signingKey as SigningKey, provider); super(signingKey as SigningKey, provider);
@ -123,12 +123,12 @@ export class Wallet extends BaseWallet {
if (progress) { progress(1); await stall(0); } if (progress) { progress(1); await stall(0); }
} else { } else {
return logger.throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]"); return throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]");
} }
const wallet = new Wallet(account.privateKey); const wallet = new Wallet(account.privateKey);
if (wallet.address !== account.address) { if (wallet.address !== account.address) {
logger.throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]"); throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]");
} }
// @TODO: mnemonic // @TODO: mnemonic
return wallet; return wallet;
@ -141,12 +141,12 @@ export class Wallet extends BaseWallet {
} else if (isCrowdsaleJson(json)) { } else if (isCrowdsaleJson(json)) {
account = decryptCrowdsaleJson(json, password); account = decryptCrowdsaleJson(json, password);
} else { } else {
return logger.throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]"); return throwArgumentError("invalid JSON wallet", "json", "[ REDACTED ]");
} }
const wallet = new Wallet(account.privateKey); const wallet = new Wallet(account.privateKey);
if (wallet.address !== account.address) { if (wallet.address !== account.address) {
logger.throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]"); throwArgumentError("address/privateKey mismatch", "json", "[ REDACTED ]");
} }
// @TODO: mnemonic // @TODO: mnemonic
return wallet; return wallet;
@ -160,7 +160,8 @@ export class Wallet extends BaseWallet {
return new Wallet(mnemonic, provider); return new Wallet(mnemonic, provider);
} }
static fromPhrase(phrase: string, provider?: null | Provider, password = "", wordlist?: Wordlist): Wallet { static fromPhrase(phrase: string, provider?: null | Provider, password?: string, wordlist?: Wordlist): Wallet {
if (password == null) { password = ""; }
return new Wallet(Mnemonic.fromPhrase(phrase, password, wordlist), provider); return new Wallet(Mnemonic.fromPhrase(phrase, password, wordlist), provider);
} }
} }

@ -1,4 +1,4 @@
import { assertArgument } from "../utils/logger.js"; import { assertArgument } from "../utils/index.js";
const subsChrs = " !#$%&'()*+,-./<=>?@[]^_`{|}~"; const subsChrs = " !#$%&'()*+,-./<=>?@[]^_`{|}~";

@ -1,4 +1,4 @@
import { assertArgument } from "../utils/logger.js"; import { assertArgument } from "../utils/index.js";
import { decodeBits } from "./bit-reader.js"; import { decodeBits } from "./bit-reader.js";
import { decodeOwl } from "./decode-owl.js"; import { decodeOwl } from "./decode-owl.js";

@ -1,5 +1,7 @@
import { id } from "../hash/id.js"; import { id } from "../hash/id.js";
import { hexlify, logger, toUtf8Bytes, toUtf8String } from "../utils/index.js"; import {
hexlify, throwArgumentError, toUtf8Bytes, toUtf8String
} from "../utils/index.js";
import { Wordlist } from "./wordlist.js"; import { Wordlist } from "./wordlist.js";
@ -135,7 +137,7 @@ class LangJa extends Wordlist {
getWord(index: number): string { getWord(index: number): string {
const words = loadWords(); const words = loadWords();
if (index < 0 || index >= words.length) { if (index < 0 || index >= words.length) {
logger.throwArgumentError(`invalid word index: ${ index }`, "index", index); throwArgumentError(`invalid word index: ${ index }`, "index", index);
} }
return words[index]; return words[index];
} }

@ -1,5 +1,5 @@
import { id } from "../hash/id.js"; import { id } from "../hash/id.js";
import { logger, toUtf8String } from "../utils/index.js"; import { throwArgumentError, toUtf8String } from "../utils/index.js";
import { Wordlist } from "./wordlist.js"; import { Wordlist } from "./wordlist.js";
@ -70,7 +70,7 @@ class LangKo extends Wordlist {
getWord(index: number): string { getWord(index: number): string {
const words = loadWords(); const words = loadWords();
if (index < 0 || index >= words.length) { if (index < 0 || index >= words.length) {
logger.throwArgumentError(`invalid word index: ${ index }`, "index", index); throwArgumentError(`invalid word index: ${ index }`, "index", index);
} }
return words[index]; return words[index];
} }

@ -1,5 +1,5 @@
import { id } from "../hash/index.js"; import { id } from "../hash/index.js";
import { toUtf8String, logger } from "../utils/index.js"; import { throwArgumentError, toUtf8String } from "../utils/index.js";
import { Wordlist } from "./wordlist.js"; import { Wordlist } from "./wordlist.js";
@ -64,7 +64,7 @@ class LangZh extends Wordlist {
getWord(index: number): string { getWord(index: number): string {
const words = loadWords(this.locale); const words = loadWords(this.locale);
if (index < 0 || index >= words.length) { if (index < 0 || index >= words.length) {
logger.throwArgumentError(`invalid word index: ${ index }`, "index", index); throwArgumentError(`invalid word index: ${ index }`, "index", index);
} }
return words[index]; return words[index];
} }

@ -3,7 +3,7 @@
// data files to be consumed by this class // data files to be consumed by this class
import { id } from "../hash/id.js"; import { id } from "../hash/id.js";
import { logger } from "../utils/logger.js"; import { throwArgumentError } from "../utils/index.js";
import { decodeOwl } from "./decode-owl.js"; import { decodeOwl } from "./decode-owl.js";
import { Wordlist } from "./wordlist.js"; import { Wordlist } from "./wordlist.js";
@ -46,7 +46,7 @@ export class WordlistOwl extends Wordlist {
getWord(index: number): string { getWord(index: number): string {
const words = this.#loadWords(); const words = this.#loadWords();
if (index < 0 || index >= words.length) { if (index < 0 || index >= words.length) {
logger.throwArgumentError(`invalid word index: ${ index }`, "index", index); throwArgumentError(`invalid word index: ${ index }`, "index", index);
} }
return words[index]; return words[index];
} }