From bbcfb5f6b88800b8ef068e4a2923675503320e33 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 1 Jun 2023 17:42:48 -0400 Subject: [PATCH] docs: added jsdocs and general documentation --- docs.wrm/basics/abi.wrm | 99 ++++- docs.wrm/basics/index.wrm | 9 +- docs.wrm/links/javascript.txt | 1 + docs.wrm/links/projects.txt | 1 + docs.wrm/links/specs.txt | 1 + src.ts/abi/abi-coder.ts | 5 +- src.ts/abi/fragments.ts | 132 ++++-- src.ts/abi/index.ts | 6 +- src.ts/abi/interface.ts | 130 +++++- src.ts/abi/typed.ts | 509 ++++++++++++++++++++++++ src.ts/contract/contract.ts | 116 +++++- src.ts/contract/factory.ts | 42 ++ src.ts/contract/index.ts | 5 +- src.ts/contract/types.ts | 155 +++++++- src.ts/contract/wrappers.ts | 95 ++++- src.ts/crypto/index.ts | 4 + src.ts/hash/index.ts | 3 +- src.ts/hash/typed-data.ts | 115 ++++++ src.ts/providers/abstract-provider.ts | 231 ++++++++++- src.ts/providers/abstract-signer.ts | 41 +- src.ts/providers/contracts.ts | 33 +- src.ts/providers/ens-resolver.ts | 42 +- src.ts/providers/formatting.ts | 279 ++++++++++--- src.ts/providers/index.ts | 9 +- src.ts/providers/network.ts | 13 +- src.ts/providers/plugins-network.ts | 124 +++++- src.ts/providers/provider-browser.ts | 26 +- src.ts/providers/provider-fallback.ts | 82 +++- src.ts/providers/provider-ipcsocket.ts | 9 + src.ts/providers/provider-jsonrpc.ts | 87 +++- src.ts/providers/provider-socket.ts | 67 +++- src.ts/providers/provider-websocket.ts | 17 + src.ts/providers/provider.ts | 490 ++++++++++++++++++++++- src.ts/providers/signer-noncemanager.ts | 19 + src.ts/providers/subscriber-filterid.ts | 18 + src.ts/providers/subscriber-polling.ts | 35 +- src.ts/transaction/index.ts | 2 +- src.ts/transaction/transaction.ts | 5 + src.ts/utils/errors.ts | 13 +- src.ts/utils/events.ts | 4 +- src.ts/utils/fetch.ts | 25 +- src.ts/utils/fixednumber.ts | 7 +- src.ts/utils/maths.ts | 4 + src.ts/wordlists/wordlist-owl.ts | 8 +- src.ts/wordlists/wordlist-owla.ts | 13 +- 45 files changed, 2923 insertions(+), 208 deletions(-) diff --git a/docs.wrm/basics/abi.wrm b/docs.wrm/basics/abi.wrm index 54df7aae9..2eab2a7e9 100644 --- a/docs.wrm/basics/abi.wrm +++ b/docs.wrm/basics/abi.wrm @@ -1,26 +1,97 @@ -_section: ABI +_section: Application Binary Interfaces @ -Explain about ABI here, what it does and does not do +When interacting with any application, whether it is on Ethereum, +over the internet or within a compiled application on a computer +all information is stored and sent as binary data which is just a +sequence of bytes. -- encodes arbitrary (but defined) data structures to raw bytes +So every application must agree on how to encode and decode their +information as a sequence of bytes. -_subsection: Call +An **Application Binary Interface** (ABI) provides a way to describe +the encoding and decoding process, in a generic way so that a variety +of types and structures of types can be defined. -About encoding +For example, a string is often encoded as a UTF-8 sequence of bytes, +which uses specific bits within sub-sequences to indicate emoji and +other special characters. Every implementation of UTF-8 must understand +and operate under the same rules so that strings work universally. In +this way, UTF-8 standard is itself an ABI. -_heading: selector or signature hash (sighash) +When interacting with Ethereum, a contract received a sequence of bytes +as input (provided by sending a transaction or through a call) and +returns a result as a sequence of bytes. So, each Contract has its own +ABI that helps specify how to encode the input and how to decode the output. -_heading: Results +It is up to the contract developer to make this ABI available. Many +Contracts implement a standard (such as ERC-20), in which case the +ABI for that standard can be used. Many developers choose to verify their +source code on Etherscan, in which case Etherscan computes the ABI and +provides it through their website (which can be fetched using the ``getContract`` +method). Otherwise, beyond reverse engineering the Contract there is +not a meaningful way to extract the contract ABI. -_subsection: Events +_subsection: Call Data Representation + +When calling a Contract on Ethereum, the input data must be encoded +according to the ABI. + +The first 4 bytes of the data are the **method selector**, which is +the keccak256 hash of the normalized method signature. + +Then the method parameters are encoded and concatenated to the selector. + +All encoded data is made up of components padded to 32 bytes, so the length +of input data will always be congruent to ``4 mod 32``. + +The result of a successful call will be encoded values, whose components +are padded to 32 bytes each as well, so the length of a result will always +be congruent to ``0 mod 32``, on success. + +The result of a reverted call will contain the **error selector** as the +first 4 bytes, which is the keccak256 of the normalized error signature, +followed by the encoded values, whose components are padded to 32 bytes +each, so the length of a revert will be congruent to ``4 mod 32``. + +The one exception to all this is that ``revert(false)`` will return a +result or ``0x``. + + +_subsection: Event Data Representation + +When an Event is emitted from a contract, there are two places data is +logged in a Log: the **topics** and the **data**. + +An additonal fee is paid for each **topic**, but this affords a topic +to be indexed in a bloom filter within the block, which allows efficient +filtering. + +The **topic hash** is always the first topic in a Log, which is the +keccak256 of the normalized event signature. This allows a specific +event to be efficiently filtered, finding the matching events in a block. + +Each additional **indexed** parameter (i.e. parameters marked with +``indexed`` in the signautre) are placed in the topics as well, but may be +filtered to find matching values. + +All non-indexed parameters are encoded and placed in the **data**. This +is cheaper and more compact, but does not allow filtering on these values. + +For example, the event ``Transfer(address indexed from, address indexed to, uint value)`` +would require 3 topics, which are the topic hash, the ``from`` address +and the ``to`` address and the data would contain 32 bytes, which is +the padded big-endian representation of ``value``. This allows for +efficient filtering by the event (i.e. ``Transfer``) as well as the ``from`` +address and ``to`` address. _subsection: Deployment -About initcode +When deploying a transaction, the data provided is treated as **initcode**, +which executes the data as normal EVM bytecode, which returns a sequence +of bytes, but instead of that sequence of bytes being treated as data that +result is instead the bytecode to install as the bytecode of the contract. -Another paragraph [[link-bip-39]]. - -_heading: Foo? - -Bar? +The bytecode produced by Solidity is designed to have all constructor +parameters encoded normally and concatenated to the bytecode and provided +as the ``data`` to a transaction with no ``to`` address. diff --git a/docs.wrm/basics/index.wrm b/docs.wrm/basics/index.wrm index 549835bd4..e657d0494 100644 --- a/docs.wrm/basics/index.wrm +++ b/docs.wrm/basics/index.wrm @@ -1,7 +1,8 @@ -_section: Ethereum Basics @priority<99> +_section: Ethereum Basics @ @priority<99> -Prelude here... +This section aims to cover some of the basics for those interested +in a deeper understanding of the inner-workings of Ethereum. -_subsection: About stuff? +_subsection: Topics -Some info +- [Application Binary Interface](docs-abi) diff --git a/docs.wrm/links/javascript.txt b/docs.wrm/links/javascript.txt index 52fc7917c..5434eb712 100644 --- a/docs.wrm/links/javascript.txt +++ b/docs.wrm/links/javascript.txt @@ -1,6 +1,7 @@ link-js-array [link-js-array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) link-js-bigint [link-js-bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) link-js-date [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +link-js-fetch [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) link-js-normalize [String.normalize](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) link-js-maxsafe [link-js-maxsafe](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER#Description) link-js-proxy [link-js-proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) diff --git a/docs.wrm/links/projects.txt b/docs.wrm/links/projects.txt index 8081ca8d9..947c68f10 100644 --- a/docs.wrm/links/projects.txt +++ b/docs.wrm/links/projects.txt @@ -12,6 +12,7 @@ link-infura [INFURA](https://infura.io) link-javascriptcore [JavaScriptCore](https://developer.apple.com/documentation/javascriptcore?language=objc) link-ledger [Ledger](https://www.ledger.com) link-metamask [MetaMask](https://metamask.io/) +link-node [Node.js](https://nodejs.org/) link-otto [Otto](https://github.com/robertkrimen/otto) link-parity [Parity](https://www.parity.io) link-pocket [Pocket Network](https://pokt.network) diff --git a/docs.wrm/links/specs.txt b/docs.wrm/links/specs.txt index 14ffb668a..afa985121 100644 --- a/docs.wrm/links/specs.txt +++ b/docs.wrm/links/specs.txt @@ -43,6 +43,7 @@ link-namehash [namehash](https://docs.ens.domains/contract-api-reference/name-pr link-rlp [Recursive-Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/) link-pbkdf2 [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) link-solc-abi [ABI Specification](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#formal-specification-of-the-encoding) +link-solc-jsonabi [ABI JSON Specification](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#json) link-solc-errors [Solidity Custom Errors](https://docs.soliditylang.org/en/v0.8.4/abi-spec.html#errors) link-solc-events [Solidity Events](https://docs.soliditylang.org/en/v0.8.4/abi-spec.html#events) link-solc-output [solc standard output](https://solidity.readthedocs.io/en/v0.6.0/using-the-compiler.html#output-description) diff --git a/src.ts/abi/abi-coder.ts b/src.ts/abi/abi-coder.ts index 352b95f42..ba45478a1 100644 --- a/src.ts/abi/abi-coder.ts +++ b/src.ts/abi/abi-coder.ts @@ -121,8 +121,9 @@ function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | } /** - * About AbiCoder - */ + * The **AbiCoder** is a low-level class responsible for encoding JavaScript + * values into binary data and decoding binary data into JavaScript values. + */ export class AbiCoder { #getCoder(param: ParamType): Coder { diff --git a/src.ts/abi/fragments.ts b/src.ts/abi/fragments.ts index 52beae436..aa824010d 100644 --- a/src.ts/abi/fragments.ts +++ b/src.ts/abi/fragments.ts @@ -1,5 +1,11 @@ /** - * About frgaments... + * A fragment is a single item from an ABI, which may represent any of: + * + * - [Functions](FunctionFragment) + * - [Events](EventFragment) + * - [Constructors](ConstructorFragment) + * - Custom [Errors](ErrorFragment) + * - [Fallback or Receive](FallbackFragment) functions * * @_subsection api/abi/abi-coder:Fragments [about-fragments] */ @@ -11,7 +17,7 @@ import { import { id } from "../hash/index.js"; /** - * A type description in a JSON API. + * A Type description in a [JSON ABI format](link-solc-jsonabi). */ export interface JsonFragmentType { /** @@ -41,7 +47,7 @@ export interface JsonFragmentType { } /** - * A fragment for a method, event or error in a JSON API. + * A fragment for a method, event or error in a [JSON ABI format](link-solc-jsonabi). */ export interface JsonFragment { /** @@ -92,19 +98,21 @@ export interface JsonFragment { /** * The format to serialize the output as. + * + * **``"sighash"``** - the bare formatting, used to compute the selector + * or topic hash; this format cannot be reversed (as it discards ``indexed``) + * so cannot by used to export an [[Interface]]. + * + * **``"minimal"``** - Human-Readable ABI with minimal spacing and without + * names, so it is compact, but will result in Result objects that cannot + * be accessed by name. + * + * **``"full"``** - Full Human-Readable ABI, with readable spacing and names + * intact; this is generally the recommended format. + * + * **``"json"``** - The [JSON ABI format](link-solc-jsonabi). */ -export type FormatType = - // Bare formatting, as is needed for computing a sighash of an event or function - "sighash" | - - // Human-Readable with Minimal spacing and without names (compact human-readable) - "minimal" | - - // Human-Readable with nice spacing, including all names - "full" | - - // JSON-format a la Solidity - "json"; +export type FormatType = "sighash" | "minimal" | "full" | "json"; // [ "a", "b" ] => { "a": 1, "b": 1 } function setify(items: Array): ReadonlySet { @@ -513,7 +521,7 @@ const FunctionFragmentInternal = "_FunctionInternal"; const StructFragmentInternal = "_StructInternal"; /** - * Each input and output of a [[Fragment]] is an Array of **PAramType**. + * Each input and output of a [[Fragment]] is an Array of **ParamType**. */ export class ParamType { @@ -642,16 +650,6 @@ export class ParamType { return result; } - /* - * Returns true if %%value%% is an Array type. - * - * This provides a type gaurd ensuring that the - * [[arrayChildren]] and [[arrayLength]] are non-null. - */ - //static isArray(value: any): value is { arrayChildren: ParamType, arrayLength: number } { - // return value && (value.baseType === "array") - //} - /** * Returns true if %%this%% is an Array type. * @@ -913,7 +911,7 @@ export abstract class Fragment { } /** - * Returns a string representation of this fragment. + * Returns a string representation of this fragment as %%format%%. */ abstract format(format?: FormatType): string; @@ -1050,6 +1048,9 @@ export class ErrorFragment extends NamedFragment { return id(this.format("sighash")).substring(0, 10); } + /** + * Returns a string representation of this fragment as %%format%%. + */ format(format?: FormatType): string { if (format == null) { format = "sighash"; } if (format === "json") { @@ -1066,6 +1067,9 @@ export class ErrorFragment extends NamedFragment { return result.join(" "); } + /** + * Returns a new **ErrorFragment** for %%obj%%. + */ static from(obj: any): ErrorFragment { if (ErrorFragment.isFragment(obj)) { return obj; } @@ -1084,6 +1088,10 @@ export class ErrorFragment extends NamedFragment { obj.inputs ? obj.inputs.map(ParamType.from): [ ]); } + /** + * Returns ``true`` and provides a type guard if %%value%% is an + * **ErrorFragment**. + */ static isFragment(value: any): value is ErrorFragment { return (value && value[internal] === ErrorFragmentInternal); } @@ -1093,6 +1101,9 @@ export class ErrorFragment extends NamedFragment { * A Fragment which represents an Event. */ export class EventFragment extends NamedFragment { + /** + * Whether this event is anonymous. + */ readonly anonymous!: boolean; /** @@ -1111,6 +1122,9 @@ export class EventFragment extends NamedFragment { return id(this.format("sighash")); } + /** + * Returns a string representation of this event as %%format%%. + */ format(format?: FormatType): string { if (format == null) { format = "sighash"; } if (format === "json") { @@ -1129,12 +1143,18 @@ export class EventFragment extends NamedFragment { return result.join(" "); } + /** + * Return the topic hash for an event with %%name%% and %%params%%. + */ static getTopicHash(name: string, params?: Array): string { params = (params || []).map((p) => ParamType.from(p)); const fragment = new EventFragment(_guard, name, params, false); return fragment.topicHash; } + /** + * Returns a new **EventFragment** for %%obj%%. + */ static from(obj: any): EventFragment { if (EventFragment.isFragment(obj)) { return obj; } @@ -1154,6 +1174,10 @@ export class EventFragment extends NamedFragment { obj.inputs ? obj.inputs.map((p: any) => ParamType.from(p, true)): [ ], !!obj.anonymous); } + /** + * Returns ``true`` and provides a type guard if %%value%% is an + * **EventFragment**. + */ static isFragment(value: any): value is EventFragment { return (value && value[internal] === EventFragmentInternal); } @@ -1163,7 +1187,15 @@ export class EventFragment extends NamedFragment { * A Fragment which represents a constructor. */ export class ConstructorFragment extends Fragment { + + /** + * Whether the constructor can receive an endowment. + */ readonly payable!: boolean; + + /** + * The recommended gas limit for deployment or ``null``. + */ readonly gas!: null | bigint; /** @@ -1175,6 +1207,9 @@ export class ConstructorFragment extends Fragment { defineProperties(this, { payable, gas }); } + /** + * Returns a string representation of this constructor as %%format%%. + */ format(format?: FormatType): string { assert(format != null && format !== "sighash", "cannot format a constructor for sighash", "UNSUPPORTED_OPERATION", { operation: "format(sighash)" }); @@ -1195,6 +1230,9 @@ export class ConstructorFragment extends Fragment { return result.join(" "); } + /** + * Returns a new **ConstructorFragment** for %%obj%%. + */ static from(obj: any): ConstructorFragment { if (ConstructorFragment.isFragment(obj)) { return obj; } @@ -1216,6 +1254,10 @@ export class ConstructorFragment extends Fragment { !!obj.payable, (obj.gas != null) ? obj.gas: null); } + /** + * Returns ``true`` and provides a type guard if %%value%% is a + * **ConstructorFragment**. + */ static isFragment(value: any): value is ConstructorFragment { return (value && value[internal] === ConstructorFragmentInternal); } @@ -1237,6 +1279,9 @@ export class FallbackFragment extends Fragment { defineProperties(this, { payable }); } + /** + * Returns a string representation of this fallback as %%format%%. + */ format(format?: FormatType): string { const type = ((this.inputs.length === 0) ? "receive": "fallback"); @@ -1248,6 +1293,9 @@ export class FallbackFragment extends Fragment { return `${ type }()${ this.payable ? " payable": "" }`; } + /** + * Returns a new **FallbackFragment** for %%obj%%. + */ static from(obj: any): FallbackFragment { if (FallbackFragment.isFragment(obj)) { return obj; } @@ -1310,6 +1358,10 @@ export class FallbackFragment extends Fragment { assertArgument(false, "invalid fallback description", "obj", obj); } + /** + * Returns ``true`` and provides a type guard if %%value%% is a + * **FallbackFragment**. + */ static isFragment(value: any): value is FallbackFragment { return (value && value[internal] === FallbackFragmentInternal); } @@ -1342,7 +1394,7 @@ export class FunctionFragment extends NamedFragment { readonly payable!: boolean; /** - * The amount of gas to send when calling this function + * The recommended gas limit to send when calling this function. */ readonly gas!: null | bigint; @@ -1365,6 +1417,9 @@ export class FunctionFragment extends NamedFragment { return id(this.format("sighash")).substring(0, 10); } + /** + * Returns a string representation of this function as %%format%%. + */ format(format?: FormatType): string { if (format == null) { format = "sighash"; } if (format === "json") { @@ -1401,12 +1456,18 @@ export class FunctionFragment extends NamedFragment { return result.join(" "); } + /** + * Return the selector for a function with %%name%% and %%params%%. + */ static getSelector(name: string, params?: Array): string { params = (params || []).map((p) => ParamType.from(p)); const fragment = new FunctionFragment(_guard, name, "view", params, [ ], null); return fragment.selector; } + /** + * Returns a new **FunctionFragment** for %%obj%%. + */ static from(obj: any): FunctionFragment { if (FunctionFragment.isFragment(obj)) { return obj; } @@ -1458,6 +1519,10 @@ export class FunctionFragment extends NamedFragment { (obj.gas != null) ? obj.gas: null); } + /** + * Returns ``true`` and provides a type guard if %%value%% is a + * **FunctionFragment**. + */ static isFragment(value: any): value is FunctionFragment { return (value && value[internal] === FunctionFragmentInternal); } @@ -1476,10 +1541,16 @@ export class StructFragment extends NamedFragment { Object.defineProperty(this, internal, { value: StructFragmentInternal }); } + /** + * Returns a string representation of this struct as %%format%%. + */ format(): string { throw new Error("@TODO"); } + /** + * Returns a new **StructFragment** for %%obj%%. + */ static from(obj: any): StructFragment { if (typeof(obj) === "string") { return StructFragment.from(lex(obj)); @@ -1494,6 +1565,11 @@ export class StructFragment extends NamedFragment { return new StructFragment(_guard, obj.name, obj.inputs ? obj.inputs.map(ParamType.from): [ ]); } +// @TODO: fix this return type + /** + * Returns ``true`` and provides a type guard if %%value%% is a + * **StructFragment**. + */ static isFragment(value: any): value is FunctionFragment { return (value && value[internal] === StructFragmentInternal); } diff --git a/src.ts/abi/index.ts b/src.ts/abi/index.ts index 2a6873581..5b1649f49 100644 --- a/src.ts/abi/index.ts +++ b/src.ts/abi/index.ts @@ -1,5 +1,9 @@ /** - * Explain about ABI here... + * The Application Binary Interface (ABI) describes how method input + * parameters should be encoded, their results decoded, and how to + * decode events and errors. + * + * See [About ABIs](docs-abi) for more details how they are used. * * @_section api/abi:Application Binary Interface [about-abi] * @_navTitle: ABI diff --git a/src.ts/abi/interface.ts b/src.ts/abi/interface.ts index 89f8ae42d..e412b4966 100644 --- a/src.ts/abi/interface.ts +++ b/src.ts/abi/interface.ts @@ -1,5 +1,11 @@ /** - * About Interface + * The Interface class is a low-level class that accepts an + * ABI and provides all the necessary functionality to encode + * and decode paramaters to and results from methods, events + * and errors. + * + * It also provides several convenience methods to automatically + * search and find matching transactions and events to parse them. * * @_subsection api/abi:Interfaces [interfaces] */ @@ -27,13 +33,39 @@ import type { JsonFragment } from "./fragments.js"; export { checkResultErrors, Result }; +/** + * When using the [[Interface-parseLog]] to automatically match a Log to its event + * for parsing, a **LogDescription** is returned. + */ export class LogDescription { + /** + * The matching fragment for the ``topic0``. + */ readonly fragment!: EventFragment; + + /** + * The name of the Event. + */ readonly name!: string; + + /** + * The full Event signature. + */ readonly signature!: string; + + /** + * The topic hash for the Event. + */ readonly topic!: string; + + /** + * The arguments passed into the Event with ``emit``. + */ readonly args!: Result + /** + * @_ignore: + */ constructor(fragment: EventFragment, topic: string, args: Result) { const name = fragment.name, signature = fragment.format(); defineProperties(this, { @@ -42,14 +74,45 @@ export class LogDescription { } } +/** + * When using the [[Interface-parseTransaction]] to automatically match + * a transaction data to its function for parsing, + * a **TransactionDescription** is returned. + */ export class TransactionDescription { + /** + * The matching fragment from the transaction ``data``. + */ readonly fragment!: FunctionFragment; + + /** + * The name of the Function from the transaction ``data``. + */ readonly name!: string; + + /** + * The arguments passed to the Function from the transaction ``data``. + */ readonly args!: Result; + + /** + * The full Function signature from the transaction ``data``. + */ readonly signature!: string; + + /** + * The selector for the Function from the transaction ``data``. + */ readonly selector!: string; + + /** + * The ``value`` (in wei) from the transaction. + */ readonly value!: bigint; + /** + * @_ignore: + */ constructor(fragment: FunctionFragment, selector: string, args: Result, value: bigint) { const name = fragment.name, signature = fragment.format(); defineProperties(this, { @@ -58,13 +121,39 @@ export class TransactionDescription { } } +/** + * When using the [[Interface-parseError]] to automatically match an + * error for a call result for parsing, an **ErrorDescription** is returned. + */ export class ErrorDescription { + /** + * The matching fragment. + */ readonly fragment!: ErrorFragment; + + /** + * The name of the Error. + */ readonly name!: string; + + /** + * The arguments passed to the Error with ``revert``. + */ readonly args!: Result; + + /** + * The full Error signature. + */ readonly signature!: string; + + /** + * The selector for the Error. + */ readonly selector!: string; + /** + * @_ignore: + */ constructor(fragment: ErrorFragment, selector: string, args: Result) { const name = fragment.name, signature = fragment.format(); defineProperties(this, { @@ -73,14 +162,35 @@ export class ErrorDescription { } } +/** + * An **Indexed** is used as a value when a value that does not + * fit within a topic (i.e. not a fixed-length, 32-byte type). It + * is the ``keccak256`` of the value, and used for types such as + * arrays, tuples, bytes and strings. + */ export class Indexed { + /** + * The ``keccak256`` of the value logged. + */ readonly hash!: null | string; + + /** + * @_ignore: + */ readonly _isIndexed!: boolean; + /** + * Returns ``true`` if %%value%% is an **Indexed**. + * + * This provides a Type Guard for property access. + */ static isIndexed(value: any): value is Indexed { return !!(value && value._isIndexed); } + /** + * @_ignore: + */ constructor(hash: null | string) { defineProperties(this, { hash, _isIndexed: true }) } @@ -152,7 +262,23 @@ function checkNames(fragment: Fragment, type: "input" | "output", params: Array< */ /** - * @TODO + * An **InterfaceAbi** may be any supported ABI format. + * + * A string is expected to be a JSON string, which will be parsed + * using ``JSON.parse``. This means that the value **must** be a valid + * JSON string, with no stray commas, etc. + * + * An array may contain any combination of: + * - Human-Readable fragments + * - Parsed JSON fragment + * - [[Fragment]] instances + * + * A **Human-Readable Fragment** is a string which resembles a Solidity + * signature and is introduced in [this blog entry](link-ricmoo-humanreadableabi). + * For example, ``function balanceOf(address) view returns (uint)``. + * + * A **Parsed JSON Fragment** is a JavaScript Object desribed in the + * [Solidity documentation](link-solc-jsonabi). */ export type InterfaceAbi = string | ReadonlyArray; diff --git a/src.ts/abi/typed.ts b/src.ts/abi/typed.ts index a434aba00..ffbfe58a5 100644 --- a/src.ts/abi/typed.ts +++ b/src.ts/abi/typed.ts @@ -38,6 +38,10 @@ function b(value: BytesLike, size?: number): Typed { return new Typed(_gaurd, `bytes${ (size) ? size: "" }`, value, { size }); } +// @TODO: Remove this in v7, it was replaced by TypedBigInt +/** + * @_ignore: + */ export interface TypedNumber extends Typed { value: number; defaultValue(): number; @@ -45,33 +49,88 @@ export interface TypedNumber extends Typed { maxValue(): number; } +/** + * A **Typed** that represents a numeric value. + */ export interface TypedBigInt extends Typed { + /** + * The value. + */ value: bigint; + + /** + * The default value for all numeric types is ``0``. + */ defaultValue(): bigint; + + /** + * The minimum value for this type, accounting for bit-width and signed-ness. + */ minValue(): bigint; + + /** + * The minimum value for this type, accounting for bit-width. + */ maxValue(): bigint; } +/** + * A **Typed** that represents a binary sequence of data as bytes. + */ export interface TypedData extends Typed { + /** + * The value. + */ value: string; + + /** + * The default value for this type. + */ defaultValue(): string; } +/** + * A **Typed** that represents a UTF-8 sequence of bytes. + */ export interface TypedString extends Typed { + /** + * The value. + */ value: string; + + /** + * The default value for the string type is the empty string (i.e. ``""``). + */ defaultValue(): string; } const _typedSymbol = Symbol.for("_ethers_typed"); +/** + * The **Typed** class to wrap values providing explicit type information. + */ export class Typed { + + /** + * The type, as a Solidity-compatible type. + */ readonly type!: string; + + /** + * The actual value. + */ readonly value!: any; readonly #options: any; + /** + * @_ignore: + */ readonly _typedSymbol!: Symbol; + /** + * @_ignore: + */ constructor(gaurd: any, type: string, value: any, options?: any) { if (options == null) { options = null; } assertPrivate(_gaurd, gaurd, "Typed"); @@ -82,6 +141,9 @@ export class Typed { this.format(); } + /** + * Format the type as a Human-Readable type. + */ format(): string { if (this.type === "array") { throw new Error(""); @@ -94,30 +156,51 @@ export class Typed { return this.type; } + /** + * The default value returned by this type. + */ defaultValue(): string | number | bigint | Result { return 0; } + /** + * The minimum value for numeric types. + */ minValue(): string | number | bigint { return 0; } + /** + * The maximum value for numeric types. + */ maxValue(): string | number | bigint { return 0; } + /** + * Returns ``true`` and provides a type guard is this is a [[TypedBigInt]]. + */ isBigInt(): this is TypedBigInt { return !!(this.type.match(/^u?int[0-9]+$/)); } + /** + * Returns ``true`` and provides a type guard is this is a [[TypedData]]. + */ isData(): this is TypedData { return this.type.startsWith("bytes"); } + /** + * Returns ``true`` and provides a type guard is this is a [[TypedString]]. + */ isString(): this is TypedString { return (this.type === "string"); } + /** + * Returns the tuple name, if this is a tuple. Throws otherwise. + */ get tupleName(): null | string { if (this.type !== "tuple") { throw TypeError("not a tuple"); } return this.#options; @@ -127,6 +210,12 @@ export class Typed { // - `null` indicates the length is unforced, it could be dynamic // - `-1` indicates the length is dynamic // - any other value indicates it is a static array and is its length + + /** + * Returns the length of the array type or ``-1`` if it is dynamic. + * + * Throws if the type is not an array. + */ get arrayLength(): null | number { if (this.type !== "array") { throw TypeError("not an array"); } if (this.#options === true) { return -1; } @@ -134,126 +223,546 @@ export class Typed { return null; } + /** + * Returns a new **Typed** of %%type%% with the %%value%%. + */ static from(type: string, value: any): Typed { return new Typed(_gaurd, type, value); } + /** + * Return a new ``uint8`` type for %%v%%. + */ static uint8(v: BigNumberish): Typed { return n(v, 8); } + + /** + * Return a new ``uint16`` type for %%v%%. + */ static uint16(v: BigNumberish): Typed { return n(v, 16); } + + /** + * Return a new ``uint24`` type for %%v%%. + */ static uint24(v: BigNumberish): Typed { return n(v, 24); } + + /** + * Return a new ``uint32`` type for %%v%%. + */ static uint32(v: BigNumberish): Typed { return n(v, 32); } + + /** + * Return a new ``uint40`` type for %%v%%. + */ static uint40(v: BigNumberish): Typed { return n(v, 40); } + + /** + * Return a new ``uint48`` type for %%v%%. + */ static uint48(v: BigNumberish): Typed { return n(v, 48); } + + /** + * Return a new ``uint56`` type for %%v%%. + */ static uint56(v: BigNumberish): Typed { return n(v, 56); } + + /** + * Return a new ``uint64`` type for %%v%%. + */ static uint64(v: BigNumberish): Typed { return n(v, 64); } + + /** + * Return a new ``uint72`` type for %%v%%. + */ static uint72(v: BigNumberish): Typed { return n(v, 72); } + + /** + * Return a new ``uint80`` type for %%v%%. + */ static uint80(v: BigNumberish): Typed { return n(v, 80); } + + /** + * Return a new ``uint88`` type for %%v%%. + */ static uint88(v: BigNumberish): Typed { return n(v, 88); } + + /** + * Return a new ``uint96`` type for %%v%%. + */ static uint96(v: BigNumberish): Typed { return n(v, 96); } + + /** + * Return a new ``uint104`` type for %%v%%. + */ static uint104(v: BigNumberish): Typed { return n(v, 104); } + + /** + * Return a new ``uint112`` type for %%v%%. + */ static uint112(v: BigNumberish): Typed { return n(v, 112); } + + /** + * Return a new ``uint120`` type for %%v%%. + */ static uint120(v: BigNumberish): Typed { return n(v, 120); } + + /** + * Return a new ``uint128`` type for %%v%%. + */ static uint128(v: BigNumberish): Typed { return n(v, 128); } + + /** + * Return a new ``uint136`` type for %%v%%. + */ static uint136(v: BigNumberish): Typed { return n(v, 136); } + + /** + * Return a new ``uint144`` type for %%v%%. + */ static uint144(v: BigNumberish): Typed { return n(v, 144); } + + /** + * Return a new ``uint152`` type for %%v%%. + */ static uint152(v: BigNumberish): Typed { return n(v, 152); } + + /** + * Return a new ``uint160`` type for %%v%%. + */ static uint160(v: BigNumberish): Typed { return n(v, 160); } + + /** + * Return a new ``uint168`` type for %%v%%. + */ static uint168(v: BigNumberish): Typed { return n(v, 168); } + + /** + * Return a new ``uint176`` type for %%v%%. + */ static uint176(v: BigNumberish): Typed { return n(v, 176); } + + /** + * Return a new ``uint184`` type for %%v%%. + */ static uint184(v: BigNumberish): Typed { return n(v, 184); } + + /** + * Return a new ``uint192`` type for %%v%%. + */ static uint192(v: BigNumberish): Typed { return n(v, 192); } + + /** + * Return a new ``uint200`` type for %%v%%. + */ static uint200(v: BigNumberish): Typed { return n(v, 200); } + + /** + * Return a new ``uint208`` type for %%v%%. + */ static uint208(v: BigNumberish): Typed { return n(v, 208); } + + /** + * Return a new ``uint216`` type for %%v%%. + */ static uint216(v: BigNumberish): Typed { return n(v, 216); } + + /** + * Return a new ``uint224`` type for %%v%%. + */ static uint224(v: BigNumberish): Typed { return n(v, 224); } + + /** + * Return a new ``uint232`` type for %%v%%. + */ static uint232(v: BigNumberish): Typed { return n(v, 232); } + + /** + * Return a new ``uint240`` type for %%v%%. + */ static uint240(v: BigNumberish): Typed { return n(v, 240); } + + /** + * Return a new ``uint248`` type for %%v%%. + */ static uint248(v: BigNumberish): Typed { return n(v, 248); } + + /** + * Return a new ``uint256`` type for %%v%%. + */ static uint256(v: BigNumberish): Typed { return n(v, 256); } + + /** + * Return a new ``uint256`` type for %%v%%. + */ static uint(v: BigNumberish): Typed { return n(v, 256); } + /** + * Return a new ``int8`` type for %%v%%. + */ static int8(v: BigNumberish): Typed { return n(v, -8); } + + /** + * Return a new ``int16`` type for %%v%%. + */ static int16(v: BigNumberish): Typed { return n(v, -16); } + + /** + * Return a new ``int24`` type for %%v%%. + */ static int24(v: BigNumberish): Typed { return n(v, -24); } + + /** + * Return a new ``int32`` type for %%v%%. + */ static int32(v: BigNumberish): Typed { return n(v, -32); } + + /** + * Return a new ``int40`` type for %%v%%. + */ static int40(v: BigNumberish): Typed { return n(v, -40); } + + /** + * Return a new ``int48`` type for %%v%%. + */ static int48(v: BigNumberish): Typed { return n(v, -48); } + + /** + * Return a new ``int56`` type for %%v%%. + */ static int56(v: BigNumberish): Typed { return n(v, -56); } + + /** + * Return a new ``int64`` type for %%v%%. + */ static int64(v: BigNumberish): Typed { return n(v, -64); } + + /** + * Return a new ``int72`` type for %%v%%. + */ static int72(v: BigNumberish): Typed { return n(v, -72); } + + /** + * Return a new ``int80`` type for %%v%%. + */ static int80(v: BigNumberish): Typed { return n(v, -80); } + + /** + * Return a new ``int88`` type for %%v%%. + */ static int88(v: BigNumberish): Typed { return n(v, -88); } + + /** + * Return a new ``int96`` type for %%v%%. + */ static int96(v: BigNumberish): Typed { return n(v, -96); } + + /** + * Return a new ``int104`` type for %%v%%. + */ static int104(v: BigNumberish): Typed { return n(v, -104); } + + /** + * Return a new ``int112`` type for %%v%%. + */ static int112(v: BigNumberish): Typed { return n(v, -112); } + + /** + * Return a new ``int120`` type for %%v%%. + */ static int120(v: BigNumberish): Typed { return n(v, -120); } + + /** + * Return a new ``int128`` type for %%v%%. + */ static int128(v: BigNumberish): Typed { return n(v, -128); } + + /** + * Return a new ``int136`` type for %%v%%. + */ static int136(v: BigNumberish): Typed { return n(v, -136); } + + /** + * Return a new ``int144`` type for %%v%%. + */ static int144(v: BigNumberish): Typed { return n(v, -144); } + + /** + * Return a new ``int52`` type for %%v%%. + */ static int152(v: BigNumberish): Typed { return n(v, -152); } + + /** + * Return a new ``int160`` type for %%v%%. + */ static int160(v: BigNumberish): Typed { return n(v, -160); } + + /** + * Return a new ``int168`` type for %%v%%. + */ static int168(v: BigNumberish): Typed { return n(v, -168); } + + /** + * Return a new ``int176`` type for %%v%%. + */ static int176(v: BigNumberish): Typed { return n(v, -176); } + + /** + * Return a new ``int184`` type for %%v%%. + */ static int184(v: BigNumberish): Typed { return n(v, -184); } + + /** + * Return a new ``int92`` type for %%v%%. + */ static int192(v: BigNumberish): Typed { return n(v, -192); } + + /** + * Return a new ``int200`` type for %%v%%. + */ static int200(v: BigNumberish): Typed { return n(v, -200); } + + /** + * Return a new ``int208`` type for %%v%%. + */ static int208(v: BigNumberish): Typed { return n(v, -208); } + + /** + * Return a new ``int216`` type for %%v%%. + */ static int216(v: BigNumberish): Typed { return n(v, -216); } + + /** + * Return a new ``int224`` type for %%v%%. + */ static int224(v: BigNumberish): Typed { return n(v, -224); } + + /** + * Return a new ``int232`` type for %%v%%. + */ static int232(v: BigNumberish): Typed { return n(v, -232); } + + /** + * Return a new ``int240`` type for %%v%%. + */ static int240(v: BigNumberish): Typed { return n(v, -240); } + + /** + * Return a new ``int248`` type for %%v%%. + */ static int248(v: BigNumberish): Typed { return n(v, -248); } + + /** + * Return a new ``int256`` type for %%v%%. + */ static int256(v: BigNumberish): Typed { return n(v, -256); } + + /** + * Return a new ``int256`` type for %%v%%. + */ static int(v: BigNumberish): Typed { return n(v, -256); } + /** + * Return a new ``bytes1`` type for %%v%%. + */ static bytes1(v: BytesLike): Typed { return b(v, 1); } + + /** + * Return a new ``bytes2`` type for %%v%%. + */ static bytes2(v: BytesLike): Typed { return b(v, 2); } + + /** + * Return a new ``bytes3`` type for %%v%%. + */ static bytes3(v: BytesLike): Typed { return b(v, 3); } + + /** + * Return a new ``bytes4`` type for %%v%%. + */ static bytes4(v: BytesLike): Typed { return b(v, 4); } + + /** + * Return a new ``bytes5`` type for %%v%%. + */ static bytes5(v: BytesLike): Typed { return b(v, 5); } + + /** + * Return a new ``bytes6`` type for %%v%%. + */ static bytes6(v: BytesLike): Typed { return b(v, 6); } + + /** + * Return a new ``bytes7`` type for %%v%%. + */ static bytes7(v: BytesLike): Typed { return b(v, 7); } + + /** + * Return a new ``bytes8`` type for %%v%%. + */ static bytes8(v: BytesLike): Typed { return b(v, 8); } + + /** + * Return a new ``bytes9`` type for %%v%%. + */ static bytes9(v: BytesLike): Typed { return b(v, 9); } + + /** + * Return a new ``bytes10`` type for %%v%%. + */ static bytes10(v: BytesLike): Typed { return b(v, 10); } + + /** + * Return a new ``bytes11`` type for %%v%%. + */ static bytes11(v: BytesLike): Typed { return b(v, 11); } + + /** + * Return a new ``bytes12`` type for %%v%%. + */ static bytes12(v: BytesLike): Typed { return b(v, 12); } + + /** + * Return a new ``bytes13`` type for %%v%%. + */ static bytes13(v: BytesLike): Typed { return b(v, 13); } + + /** + * Return a new ``bytes14`` type for %%v%%. + */ static bytes14(v: BytesLike): Typed { return b(v, 14); } + + /** + * Return a new ``bytes15`` type for %%v%%. + */ static bytes15(v: BytesLike): Typed { return b(v, 15); } + + /** + * Return a new ``bytes16`` type for %%v%%. + */ static bytes16(v: BytesLike): Typed { return b(v, 16); } + + /** + * Return a new ``bytes17`` type for %%v%%. + */ static bytes17(v: BytesLike): Typed { return b(v, 17); } + + /** + * Return a new ``bytes18`` type for %%v%%. + */ static bytes18(v: BytesLike): Typed { return b(v, 18); } + + /** + * Return a new ``bytes19`` type for %%v%%. + */ static bytes19(v: BytesLike): Typed { return b(v, 19); } + + /** + * Return a new ``bytes20`` type for %%v%%. + */ static bytes20(v: BytesLike): Typed { return b(v, 20); } + + /** + * Return a new ``bytes21`` type for %%v%%. + */ static bytes21(v: BytesLike): Typed { return b(v, 21); } + + /** + * Return a new ``bytes22`` type for %%v%%. + */ static bytes22(v: BytesLike): Typed { return b(v, 22); } + + /** + * Return a new ``bytes23`` type for %%v%%. + */ static bytes23(v: BytesLike): Typed { return b(v, 23); } + + /** + * Return a new ``bytes24`` type for %%v%%. + */ static bytes24(v: BytesLike): Typed { return b(v, 24); } + + /** + * Return a new ``bytes25`` type for %%v%%. + */ static bytes25(v: BytesLike): Typed { return b(v, 25); } + + /** + * Return a new ``bytes26`` type for %%v%%. + */ static bytes26(v: BytesLike): Typed { return b(v, 26); } + + /** + * Return a new ``bytes27`` type for %%v%%. + */ static bytes27(v: BytesLike): Typed { return b(v, 27); } + + /** + * Return a new ``bytes28`` type for %%v%%. + */ static bytes28(v: BytesLike): Typed { return b(v, 28); } + + /** + * Return a new ``bytes29`` type for %%v%%. + */ static bytes29(v: BytesLike): Typed { return b(v, 29); } + + /** + * Return a new ``bytes30`` type for %%v%%. + */ static bytes30(v: BytesLike): Typed { return b(v, 30); } + + /** + * Return a new ``bytes31`` type for %%v%%. + */ static bytes31(v: BytesLike): Typed { return b(v, 31); } + + /** + * Return a new ``bytes32`` type for %%v%%. + */ static bytes32(v: BytesLike): Typed { return b(v, 32); } + + /** + * Return a new ``address`` type for %%v%%. + */ static address(v: string | Addressable): Typed { return new Typed(_gaurd, "address", v); } + + /** + * Return a new ``bool`` type for %%v%%. + */ static bool(v: any): Typed { return new Typed(_gaurd, "bool", !!v); } + + /** + * Return a new ``bytes`` type for %%v%%. + */ static bytes(v: BytesLike): Typed { return new Typed(_gaurd, "bytes", v); } + + /** + * Return a new ``string`` type for %%v%%. + */ static string(v: string): Typed { return new Typed(_gaurd, "string", v); } + + /** + * Return a new ``array`` type for %%v%%, allowing %%dynamic%% length. + */ static array(v: Array, dynamic?: null | boolean): Typed { throw new Error("not implemented yet"); return new Typed(_gaurd, "array", v, dynamic); } + + /** + * Return a new ``tuple`` type for %%v%%, with the optional %%name%%. + */ static tuple(v: Array | Record, name?: string): Typed { throw new Error("not implemented yet"); return new Typed(_gaurd, "tuple", v, name); } + + /** + * Return a new ``uint8`` type for %%v%%. + */ static overrides(v: Record): Typed { return new Typed(_gaurd, "overrides", Object.assign({ }, v)); } diff --git a/src.ts/contract/contract.ts b/src.ts/contract/contract.ts index d6c9e4804..a23545c6a 100644 --- a/src.ts/contract/contract.ts +++ b/src.ts/contract/contract.ts @@ -608,16 +608,49 @@ async function emit(contract: BaseContract, event: ContractEventName, args: Arra const passProperties = [ "then" ]; export class BaseContract implements Addressable, EventEmitterable { + /** + * The target to connect to. + * + * This can be an address, ENS name or any [[Addressable]], such as + * another contract. To get the resovled address, use the ``getAddress`` + * method. + */ readonly target!: string | Addressable; + + /** + * The contract Interface. + */ readonly interface!: Interface; + + /** + * The connected runner. This is generally a [[Provider]] or a + * [[Signer]], which dictates what operations are supported. + * + * For example, a **Contract** connected to a [[Provider]] may + * only execute read-only operations. + */ readonly runner!: null | ContractRunner; + /** + * All the Events available on this contract. + */ readonly filters!: Record; + /** + * @_ignore: + */ readonly [internal]: any; + /** + * The fallback or receive function if any. + */ readonly fallback!: null | WrappedFallback; + /** + * Creates a new contract connected to %%target%% with the %%abi%% and + * optionally connected to a %%runner%% to perform operations on behalf + * of. + */ constructor(target: string | Addressable, abi: Interface | InterfaceAbi, runner?: null | ContractRunner, _deployTx?: null | TransactionResponse) { assertArgument(typeof(target) === "string" || isAddressable(target), "invalid value for Contract target", "target", target); @@ -727,12 +760,22 @@ export class BaseContract implements Addressable, EventEmitterable { return await getInternal(this).addrPromise; } + /** + * Return the dedployed bytecode or null if no bytecode is found. + */ async getDeployedCode(): Promise { const provider = getProvider(this.runner); assert(provider, "runner does not support .provider", @@ -743,6 +786,10 @@ export class BaseContract implements Addressable, EventEmitterable { // We have the deployement transaction; just use that (throws if deployement fails) const deployTx = this.deploymentTransaction(); @@ -774,26 +821,50 @@ export class BaseContract implements Addressable, EventEmitterable(key: string | FunctionFragment): T { if (typeof(key) !== "string") { key = key.format(); } const func = buildWrappedMethod(this, key); return func; } + /** + * Return the event for a given name. This is useful when a contract + * event name conflicts with a JavaScript name such as ``prototype`` or + * when using a Contract programatically. + */ getEvent(key: string | EventFragment): ContractEvent { if (typeof(key) !== "string") { key = key.format(); } return buildWrappedEvent(this, key); } + /** + * @_ignore: + */ async queryTransaction(hash: string): Promise> { // Is this useful? throw new Error("@TODO"); } + /** + * Provide historic access to event data for %%event%% in the range + * %%fromBlock%% (default: ``0``) to %%toBlock%% (default: ``"latest"``) + * inclusive. + */ async queryFilter(event: ContractEventName, fromBlock?: BlockTag, toBlock?: BlockTag): Promise> { if (fromBlock == null) { fromBlock = 0; } if (toBlock == null) { toBlock = "latest"; } @@ -822,6 +893,9 @@ export class BaseContract implements Addressable, EventEmitterable { const sub = await getSub(this, "on", event); sub.listeners.push({ listener, once: false }); @@ -829,6 +903,10 @@ export class BaseContract implements Addressable, EventEmitterable { const sub = await getSub(this, "once", event); sub.listeners.push({ listener, once: true }); @@ -836,10 +914,19 @@ export class BaseContract implements Addressable, EventEmitterable): Promise { return await emit(this, event, args, null); } + /** + * Resolves to the number of listeners of %%event%% or the total number + * of listeners if unspecified. + */ async listenerCount(event?: ContractEventName): Promise { if (event) { const sub = await hasSub(this, event); @@ -856,6 +943,10 @@ export class BaseContract implements Addressable, EventEmitterable> { if (event) { const sub = await hasSub(this, event); @@ -872,6 +963,10 @@ export class BaseContract implements Addressable, EventEmitterable { const sub = await hasSub(this, event); if (!sub) { return this; } @@ -889,6 +984,10 @@ export class BaseContract implements Addressable, EventEmitterable { if (event) { const sub = await hasSub(this, event); @@ -906,16 +1005,23 @@ export class BaseContract implements Addressable, EventEmitterable { return await this.on(event, listener); } - // Alias for "off" + /** + * Alias for [off]. + */ async removeListener(event: ContractEventName, listener: Listener): Promise { return await this.off(event, listener); } + /** + * Create a new Class for the %%abi%%. + */ static buildClass(abi: InterfaceAbi): new (target: string, runner?: null | ContractRunner) => BaseContract & Omit { class CustomContract extends BaseContract { constructor(address: string, runner: null | ContractRunner = null) { @@ -925,6 +1031,9 @@ export class BaseContract implements Addressable, EventEmitterable(target: string, abi: InterfaceAbi, runner?: null | ContractRunner): BaseContract & Omit { if (runner == null) { runner = null; } const contract = new this(target, abi, runner ); @@ -936,4 +1045,7 @@ function _ContractBase(): new (target: string, abi: InterfaceAbi, runner?: null return BaseContract as any; } +/** + * A [[BaseContract]] with no type guards on its methods or events. + */ export class Contract extends _ContractBase() { } diff --git a/src.ts/contract/factory.ts b/src.ts/contract/factory.ts index 039223f23..16709ac09 100644 --- a/src.ts/contract/factory.ts +++ b/src.ts/contract/factory.ts @@ -20,11 +20,34 @@ import type { ContractTransactionResponse } from "./wrappers.js"; // A = Arguments to the constructor // I = Interface of deployed contracts + +/** + * A **ContractFactory** is used to deploy a Contract to the blockchain. + */ export class ContractFactory = Array, I = BaseContract> { + + /** + * The Contract Interface. + */ readonly interface!: Interface; + + /** + * The Contract deployment bytecode. Often called the initcode. + */ readonly bytecode!: string; + + /** + * The ContractRunner to deploy the Contract as. + */ readonly runner!: null | ContractRunner; + /** + * Create a new **ContractFactory** with %%abi%% and %%bytecode%%, + * optionally connected to %%runner%%. + * + * The %%bytecode%% may be the ``bytecode`` property within the + * standard Solidity JSON output. + */ constructor(abi: Interface | InterfaceAbi, bytecode: BytesLike | { object: string }, runner?: null | ContractRunner) { const iface = Interface.from(abi); @@ -42,6 +65,10 @@ export class ContractFactory = Array, I = BaseContract }); } + /** + * Resolves to the transaction to deploy the contract, passing %%args%% + * into the constructor. + */ async getDeployTransaction(...args: ContractMethodArgs): Promise { let overrides: Omit = { }; @@ -61,6 +88,14 @@ export class ContractFactory = Array, I = BaseContract return Object.assign({ }, overrides, { data }); } + /** + * Resolves to the Contract deployed by passing %%args%% into the + * constructor. + * + * This will resovle to the Contract before it has been deployed to the + * network, so the [[BaseContract-waitForDeployment]] should be used before + * sending any transactions to it. + */ async deploy(...args: ContractMethodArgs): Promise> { const tx = await this.getDeployTransaction(...args); @@ -73,10 +108,17 @@ export class ContractFactory = Array, I = BaseContract return new (BaseContract)(address, this.interface, this.runner, sentTx); } + /** + * Return a new **ContractFactory** with the same ABI and bytecode, + * but connected to %%runner%%. + */ connect(runner: null | ContractRunner): ContractFactory { return new ContractFactory(this.interface, this.bytecode, runner); } + /** + * Create a new **ContractFactory** from the standard Solidity JSON output. + */ static fromSolidity = Array, I = ContractInterface>(output: any, runner?: ContractRunner): ContractFactory { assertArgument(output != null, "bad compiler output", "output", output); diff --git a/src.ts/contract/index.ts b/src.ts/contract/index.ts index aec2e15ea..9a6b09e9c 100644 --- a/src.ts/contract/index.ts +++ b/src.ts/contract/index.ts @@ -1,5 +1,8 @@ /** - * About contracts... + * A **Contract** object is a meta-class (a class whose definition is + * defined at runtime), which communicates with a deployed smart contract + * on the blockchain and provides a simple JavaScript interface to call + * methods, send transaction, query historic logs and listen for its events. * * @_section: api/contract:Contracts [about-contracts] */ diff --git a/src.ts/contract/types.ts b/src.ts/contract/types.ts index 30ad89629..8b8aaeb08 100644 --- a/src.ts/contract/types.ts +++ b/src.ts/contract/types.ts @@ -8,36 +8,89 @@ import type { import type { ContractTransactionResponse } from "./wrappers.js"; -// The types of events a Contract can listen for +/** + * The name for an event used for subscribing to Contract events. + * + * **``string``** - An event by name. The event must be non-ambiguous. + * The parameters will be dereferenced when passed into the listener. + * + * [[ContractEvent]] - A filter from the ``contract.filters``, which will + * pass only the EventPayload as a single parameter, which includes a + * ``.signature`` property that can be used to further filter the event. + * + * [[TopicFilter]] - A filter defined using the standard Ethereum API + * which provides the specific topic hash or topic hashes to watch for along + * with any additional values to filter by. This will only pass a single + * parameter to the listener, the EventPayload which will include additional + * details to refine by, such as the event name and signature. + * + * [[DeferredTopicFilter]] - A filter created by calling a [[ContractEvent]] + * with parameters, which will create a filter for a specific event + * signautre and dereference each parameter when calling the listener. + */ export type ContractEventName = string | ContractEvent | TopicFilter | DeferredTopicFilter; +/** + * A Contract with no method constraints. + */ export interface ContractInterface { [ name: string ]: BaseContractMethod; }; +/** + * When creating a filter using the ``contract.filters``, this is returned. + */ export interface DeferredTopicFilter { getTopicFilter(): Promise; fragment: EventFragment; } +/** + * When populating a transaction this type is returned. + */ export interface ContractTransaction extends PreparedTransactionRequest { - // These are populated by contract methods and cannot bu null + /** + * The target address. + */ to: string; + + /** + * The transaction data. + */ data: string; - // These are resolved + /** + * The from address, if any. + */ from?: string; } -// Deployment Transactions have no `to` +/** + * A deployment transaction for a contract. + */ export interface ContractDeployTransaction extends Omit { } -// Overrides; cannot override `to` or `data` as Contract populates these +/** + * The overrides for a contract transaction. + */ export interface Overrides extends Omit { }; -// Arguments for methods; with an optional (n+1)th Override +/** + * Arguments to a Contract method can always include an additional and + * optional overrides parameter. + * + * @_ignore: + */ export type PostfixOverrides> = A | [ ...A, Overrides ]; + +/** + * Arguments to a Contract method can always include an additional and + * optional overrides parameter, and each parameter can optionally be + * [[Typed]]. + * + * @_ignore: + */ export type ContractMethodArgs> = PostfixOverrides<{ [ I in keyof A ]-?: A[I] | Typed }>; // A = Arguments passed in as a tuple @@ -45,51 +98,139 @@ export type ContractMethodArgs> = PostfixOverrides<{ [ I in // the qualified type, otherwise Result) // D = The type the default call will return (i.e. R for view/pure, // TransactionResponse otherwise) + +/** + * A Contract method can be called directly, or used in various ways. + */ export interface BaseContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { (...args: ContractMethodArgs): Promise; + /** + * The name of the Contract method. + */ name: string; + /** + * The fragment of the Contract method. This will throw on ambiguous + * method names. + */ fragment: FunctionFragment; + /** + * Returns the fragment constrained by %%args%%. This can be used to + * resolve ambiguous method names. + */ getFragment(...args: ContractMethodArgs): FunctionFragment; + /** + * Returns a populated transaction that can be used to perform the + * contract method with %%args%%. + */ populateTransaction(...args: ContractMethodArgs): Promise; + + /** + * Call the contract method with %%args%% and return the value. + * + * If the return value is a single type, it will be dereferenced and + * returned directly, otherwise the full Result will be returned. + */ staticCall(...args: ContractMethodArgs): Promise; + + /** + * Send a transaction for the contract method with %%args%%. + */ send(...args: ContractMethodArgs): Promise; + + /** + * Estimate the gas to send the contract method with %%args%%. + */ estimateGas(...args: ContractMethodArgs): Promise; + + /** + * Call the contract method with %%args%% and return the Result + * without any dereferencing. + */ staticCallResult(...args: ContractMethodArgs): Promise; } +/** + * A contract method on a Contract. + */ export interface ContractMethod< A extends Array = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse > extends BaseContractMethod { } +/** + * A pure of view method on a Contract. + */ export interface ConstantContractMethod< A extends Array, R = any > extends ContractMethod { } -// Arguments for events; with each element optional and/or nullable +/** + * Each argument of an event is nullable (to indicate matching //any//. + * + * @_ignore: + */ export type ContractEventArgs> = { [ I in keyof A ]?: A[I] | Typed | null }; export interface ContractEvent = Array> { (...args: ContractEventArgs): DeferredTopicFilter; + /** + * The name of the Contract event. + */ name: string; + /** + * The fragment of the Contract event. This will throw on ambiguous + * method names. + */ fragment: EventFragment; + + /** + * Returns the fragment constrained by %%args%%. This can be used to + * resolve ambiguous event names. + */ getFragment(...args: ContractEventArgs): EventFragment; }; +/** + * A Fallback or Receive function on a Contract. + */ export interface WrappedFallback { (overrides?: Omit): Promise; + /** + * Returns a populated transaction that can be used to perform the + * fallback method. + * + * For non-receive fallback, ``data`` may be overridden. + */ populateTransaction(overrides?: Omit): Promise; + + /** + * Call the contract fallback and return the result. + * + * For non-receive fallback, ``data`` may be overridden. + */ staticCall(overrides?: Omit): Promise; + + /** + * Send a transaction to the contract fallback. + * + * For non-receive fallback, ``data`` may be overridden. + */ send(overrides?: Omit): Promise; + + /** + * Estimate the gas to send a transaction to the contract fallback. + * + * For non-receive fallback, ``data`` may be overridden. + */ estimateGas(overrides?: Omit): Promise; } diff --git a/src.ts/contract/wrappers.ts b/src.ts/contract/wrappers.ts index 9af389d25..54c075ef3 100644 --- a/src.ts/contract/wrappers.ts +++ b/src.ts/contract/wrappers.ts @@ -14,30 +14,64 @@ import type { import type { BaseContract } from "./contract.js"; import type { ContractEventName } from "./types.js"; - +/** + * An **EventLog** contains additional properties parsed from the [[Log]]. + */ export class EventLog extends Log { + /** + * The Contract Interface. + */ readonly interface!: Interface; + + /** + * The matching event. + */ readonly fragment!: EventFragment; + + /** + * The parsed arguments passed to the event by ``emit``. + */ readonly args!: Result; + /** + * @_ignore: + */ constructor(log: Log, iface: Interface, fragment: EventFragment) { super(log, log.provider); const args = iface.decodeEventLog(fragment, log.data, log.topics); defineProperties(this, { args, fragment, interface: iface }); } + /** + * The name of the event. + */ get eventName(): string { return this.fragment.name; } + + /** + * The signature of the event. + */ get eventSignature(): string { return this.fragment.format(); } } +/** + * A **ContractTransactionReceipt** includes the parsed logs from a + * [[TransactionReceipt]]. + */ export class ContractTransactionReceipt extends TransactionReceipt { readonly #iface: Interface; + /** + * @_ignore: + */ constructor(iface: Interface, provider: Provider, tx: TransactionReceipt) { super(tx, provider); this.#iface = iface; } + /** + * The parsed logs for any [[Log]] which has a matching event in the + * Contract ABI. + */ get logs(): Array { return super.logs.map((log) => { const fragment = log.topics.length ? this.#iface.getEvent(log.topics[0]): null; @@ -51,14 +85,30 @@ export class ContractTransactionReceipt extends TransactionReceipt { } +/** + * A **ContractTransactionResponse** will return a + * [[ContractTransactionReceipt]] when waited on. + */ export class ContractTransactionResponse extends TransactionResponse { readonly #iface: Interface; + /** + * @_ignore: + */ constructor(iface: Interface, provider: Provider, tx: TransactionResponse) { super(tx, provider); this.#iface = iface; } + /** + * Resolves once this transaction has been mined and has + * %%confirms%% blocks including it (default: ``1``) with an + * optional %%timeout%%. + * + * This can resolve to ``null`` only if %%confirms%% is ``0`` + * and the transaction has not been mined, otherwise this will + * wait until enough confirmations have completed. + */ async wait(confirms?: number): Promise { const receipt = await super.wait(); if (receipt == null) { return null; } @@ -66,43 +116,86 @@ export class ContractTransactionResponse extends TransactionResponse { } } +/** + * A **ContractUnknownEventPayload** is included as the last parameter to + * Contract Events when the event does not match any events in the ABI. + */ export class ContractUnknownEventPayload extends EventPayload { + /** + * The log with no matching events. + */ readonly log!: Log; + /** + * @_event: + */ constructor(contract: BaseContract, listener: null | Listener, filter: ContractEventName, log: Log) { super(contract, listener, filter); defineProperties(this, { log }); } + /** + * Resolves to the block the event occured in. + */ async getBlock(): Promise { return await this.log.getBlock(); } + /** + * Resolves to the transaction the event occured in. + */ async getTransaction(): Promise { return await this.log.getTransaction(); } + /** + * Resolves to the transaction receipt the event occured in. + */ async getTransactionReceipt(): Promise { return await this.log.getTransactionReceipt(); } } +/** + * A **ContractEventPayload** is included as the last parameter to + * Contract Events when the event is known. + */ export class ContractEventPayload extends ContractUnknownEventPayload { + /** + * The matching event. + */ declare readonly fragment: EventFragment; + + /** + * The log, with parsed properties. + */ declare readonly log: EventLog; + + /** + * The parsed arguments passed to the event by ``emit``. + */ declare readonly args: Result; + /** + * @_ignore: + */ constructor(contract: BaseContract, listener: null | Listener, filter: ContractEventName, fragment: EventFragment, _log: Log) { super(contract, listener, filter, new EventLog(_log, contract.interface, fragment)); const args = contract.interface.decodeEventLog(fragment, this.log.data, this.log.topics); defineProperties(this, { args, fragment }); } + /** + * The event name. + */ get eventName(): string { return this.fragment.name; } + /** + * The event signature. + */ get eventSignature(): string { return this.fragment.format(); } diff --git a/src.ts/crypto/index.ts b/src.ts/crypto/index.ts index fef23693f..296bf81d0 100644 --- a/src.ts/crypto/index.ts +++ b/src.ts/crypto/index.ts @@ -32,6 +32,10 @@ export { export { SigningKey } from "./signing-key.js"; export { Signature } from "./signature.js"; +/** + * Once called, prevents any future change to the underlying cryptographic + * primitives using the ``.register`` feature for hooks. + */ function lock(): void { computeHmac.lock(); keccak256.lock(); diff --git a/src.ts/hash/index.ts b/src.ts/hash/index.ts index 1ac1538a3..a8c07ff60 100644 --- a/src.ts/hash/index.ts +++ b/src.ts/hash/index.ts @@ -1,5 +1,6 @@ /** - * About hashing here... + * Utilities for common tasks involving hashing. Also see + * [cryptographic hashing](about-crypto-hashing). * * @_section: api/hashing:Hashing Utilities [about-hashing] */ diff --git a/src.ts/hash/typed-data.ts b/src.ts/hash/typed-data.ts index 3fdbd8c41..20d2bbf40 100644 --- a/src.ts/hash/typed-data.ts +++ b/src.ts/hash/typed-data.ts @@ -21,16 +21,50 @@ const BN_0 = BigInt(0); const BN_1 = BigInt(1); const BN_MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); +// @TODO: in v7, verifyingContract should be an AddressLike and use resolveAddress + +/** + * The domain for an [[link-eip-712]] payload. + */ export interface TypedDataDomain { + /** + * The human-readable name of the signing domain. + */ name?: null | string; + + /** + * The major version of the signing domain. + */ version?: null | string; + + /** + * The chain ID of the signing domain. + */ chainId?: null | BigNumberish; + + /** + * The the address of the contract that will verify the signature. + */ verifyingContract?: null | string; + + /** + * A salt used for purposes decided by the specific domain. + */ salt?: null | BytesLike; }; +/** + * A specific field of a structured [[link-eip-712]] type. + */ export interface TypedDataField { + /** + * The field name. + */ name: string; + + /** + * The type of the field. + */ type: string; }; @@ -147,10 +181,30 @@ function encodeType(name: string, fields: Array): string { return `${ name }(${ fields.map(({ name, type }) => (type + " " + name)).join(",") })`; } +/** + * A **TypedDataEncode** prepares and encodes [[link-eip-712]] payloads + * for signed typed data. + * + * This is useful for those that wish to compute various components of a + * typed data hash, primary types, or sub-components, but generally the + * higher level [[Signer-signTypedData]] is more useful. + */ export class TypedDataEncoder { + /** + * The primary type for the structured [[types]]. + * + * This is derived automatically from the [[types]], since no + * recursion is possible, once the DAG for the types is consturcted + * internally, the primary type must be the only remaining type with + * no parent nodes. + */ readonly primaryType!: string; readonly #types: string; + + /** + * The types. + */ get types(): Record> { return JSON.parse(this.#types); } @@ -159,6 +213,13 @@ export class TypedDataEncoder { readonly #encoderCache: Map string>; + /** + * Create a new **TypedDataEncoder** for %%types%%. + * + * This performs all necessary checking that types are valid and + * do not violate the [[link-eip-712]] structural constraints as + * well as computes the [[primaryType]]. + */ constructor(types: Record>) { this.#types = JSON.stringify(types); this.#fullTypes = new Map(); @@ -241,6 +302,9 @@ export class TypedDataEncoder { } } + /** + * Returnthe encoder for the specific %%type%%. + */ getEncoder(type: string): (value: any) => string { let encoder = this.#encoderCache.get(type); if (!encoder) { @@ -293,28 +357,46 @@ export class TypedDataEncoder { assertArgument(false, `unknown type: ${ type }`, "type", type); } + /** + * Return the full type for %%name%%. + */ encodeType(name: string): string { const result = this.#fullTypes.get(name); assertArgument(result, `unknown type: ${ JSON.stringify(name) }`, "name", name); return result; } + /** + * Return the encoded %%value%% for the %%type%%. + */ encodeData(type: string, value: any): string { return this.getEncoder(type)(value); } + /** + * Returns the hash of %%value%% for the type of %%name%%. + */ hashStruct(name: string, value: Record): string { return keccak256(this.encodeData(name, value)); } + /** + * Return the fulled encoded %%value%% for the [[types]]. + */ encode(value: Record): string { return this.encodeData(this.primaryType, value); } + /** + * Return the hash of the fully encoded %%value%% for the [[types]]. + */ hash(value: Record): string { return this.hashStruct(this.primaryType, value); } + /** + * @_ignore: + */ _visit(type: string, value: any, callback: (type: string, data: any) => any): any { // Basic encoder type (address, bool, uint256, etc) { @@ -341,22 +423,41 @@ export class TypedDataEncoder { assertArgument(false, `unknown type: ${ type }`, "type", type); } + /** + * Call %%calback%% for each value in %%value%%, passing the type and + * component within %%value%%. + * + * This is useful for replacing addresses or other transformation that + * may be desired on each component, based on its type. + */ visit(value: Record, callback: (type: string, data: any) => any): any { return this._visit(this.primaryType, value, callback); } + /** + * Create a new **TypedDataEncoder** for %%types%%. + */ static from(types: Record>): TypedDataEncoder { return new TypedDataEncoder(types); } + /** + * Return the primary type for %%types%%. + */ static getPrimaryType(types: Record>): string { return TypedDataEncoder.from(types).primaryType; } + /** + * Return the hashed struct for %%value%% using %%types%% and %%name%%. + */ static hashStruct(name: string, types: Record>, value: Record): string { return TypedDataEncoder.from(types).hashStruct(name, value); } + /** + * Return the domain hash for %%domain%%. + */ static hashDomain(domain: TypedDataDomain): string { const domainFields: Array = [ ]; for (const name in domain) { @@ -373,6 +474,9 @@ export class TypedDataEncoder { return TypedDataEncoder.hashStruct("EIP712Domain", { EIP712Domain: domainFields }, domain); } + /** + * Return the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%. + */ static encode(domain: TypedDataDomain, types: Record>, value: Record): string { return concat([ "0x1901", @@ -381,11 +485,18 @@ export class TypedDataEncoder { ]); } + /** + * Return the hash of the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%. + */ static hash(domain: TypedDataDomain, types: Record>, value: Record): string { return keccak256(TypedDataEncoder.encode(domain, types, value)); } // Replaces all address types with ENS names with their looked up address + /** + * Resolves to the value from resolving all addresses in %%value%% for + * %%types%% and the %%domain%%. + */ static async resolveNames(domain: TypedDataDomain, types: Record>, value: Record, resolveName: (name: string) => Promise): Promise<{ domain: TypedDataDomain, value: any }> { // Make a copy to isolate it from the object passed in domain = Object.assign({ }, domain); @@ -435,6 +546,10 @@ export class TypedDataEncoder { return { domain, value }; } + /** + * Returns the JSON-encoded payload expected by nodes which implement + * the JSON-RPC [[link-eip-712]] method. + */ static getPayload(domain: TypedDataDomain, types: Record>, value: Record): any { // Validate the domain fields TypedDataEncoder.hashDomain(domain); diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index 36aaffe0a..ab2307895 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -1,5 +1,7 @@ /** - * About Subclassing the Provider... + * The available providers should suffice for most developers purposes, + * but the [[AbstractProvider]] class has many features which enable + * sub-classing it for specific purposes. * * @_section: api/providers/abstract-provider: Subclassing Provider [abstract-provider] */ @@ -86,6 +88,10 @@ function getTag(prefix: string, value: any): string { }); } +/** + * The types of additional event values that can be emitted for the + * ``"debug"`` event. + */ export type DebugEventAbstractProvider = { action: "sendCcipReadFetchRequest", request: FetchRequest @@ -113,7 +119,12 @@ export type DebugEventAbstractProvider = { }; -// Only sub-classes overriding the _getSubscription method will care about this +/** + * The value passed to the [[AbstractProvider-_getSubscriber]] method. + * + * Only developers sub-classing [[AbstractProvider[[ will care about this, + * if they are modifying a low-level feature of how subscriptions operate. + */ export type Subscription = { type: "block" | "close" | "debug" | "error" | "network" | "pending", tag: string @@ -131,22 +142,59 @@ export type Subscription = { filter: OrphanFilter }; +/** + * A **Subscriber** manages a subscription. + * + * Only developers sub-classing [[AbstractProvider[[ will care about this, + * if they are modifying a low-level feature of how subscriptions operate. + */ export interface Subscriber { + /** + * Called initially when a subscriber is added the first time. + */ start(): void; + + /** + * Called when there are no more subscribers to the event. + */ stop(): void; + /** + * Called when the subscription should pause. + * + * If %%dropWhilePaused%%, events that occur while paused should not + * be emitted [[resume]]. + */ pause(dropWhilePaused?: boolean): void; + + /** + * Resume a paused subscriber. + */ resume(): void; - // Subscribers which use polling should implement this to allow - // Providers the ability to update underlying polling intervals - // If not supported, accessing this property should return undefined + /** + * The frequency (in ms) to poll for events, if polling is used by + * the subscriber. + * + * For non-polling subscribers, this must return ``undefined``. + */ pollingInterval?: number; } +/** + * An **UnmanagedSubscriber** is useful for events which do not require + * any additional management, such as ``"debug"`` which only requires + * emit in synchronous event loop triggered calls. + */ export class UnmanagedSubscriber implements Subscriber { + /** + * The name fof the event. + */ name!: string; + /** + * Create a new UnmanagedSubscriber with %%name%%. + */ constructor(name: string) { defineProperties(this, { name }); } start(): void { } @@ -247,11 +295,26 @@ async function getSubscription(_event: ProviderEvent, provider: AbstractProvider function getTime(): number { return (new Date()).getTime(); } +/** + * An **AbstractPlugin** is used to provide additional internal services + * to an [[AbstractProvider]] without adding backwards-incompatible changes + * to method signatures or other internal and complex logic. + */ export interface AbstractProviderPlugin { + /** + * The reverse domain notation of the plugin. + */ readonly name: string; + + /** + * Creates a new instance of the plugin, connected to %%provider%%. + */ connect(provider: AbstractProvider): AbstractProviderPlugin; } +/** + * A normalized filter used for [[PerformActionRequest]] objects. + */ export type PerformActionFilter = { address?: string | Array; topics?: Array>; @@ -263,11 +326,25 @@ export type PerformActionFilter = { blockHash?: string; }; +/** + * A normalized transactions used for [[PerformActionRequest]] objects. + */ export interface PerformActionTransaction extends PreparedTransactionRequest { + /** + * The ``to`` address of the transaction. + */ to?: string; + + /** + * The sender of the transaction. + */ from?: string; } +/** + * The [[AbstractProvider]] methods will normalize all values and pass this + * type to [[AbstractProvider-_perform]]. + */ export type PerformActionRequest = { method: "broadcastTransaction", signedTransaction: string @@ -330,7 +407,12 @@ type CcipArgs = { errorArgs: Array }; - +/** + * An **AbstractProvider** provides a base class for other sub-classes to + * implement the [[Provider]] API by normalizing input arguments and + * formatting output results as well as tracking events for consistent + * behaviour on an eventually-consistent network. + */ export class AbstractProvider implements Provider { #subs: Map; @@ -352,8 +434,11 @@ export class AbstractProvider implements Provider { #disableCcipRead: boolean; - // @TODO: This should be a () => Promise so network can be - // done when needed; or rely entirely on _detectNetwork? + /** + * Create a new **AbstractProvider** connected to %%network%%, or + * use the various network detection capabilities to discover the + * [[Network]] if necessary. + */ constructor(_network?: "any" | Networkish) { if (_network === "any") { @@ -383,12 +468,22 @@ export class AbstractProvider implements Provider { this.#disableCcipRead = false; } + /** + * Returns ``this``, to allow an **AbstractProvider** to implement + * the [[ContractRunner]] interface. + */ get provider(): this { return this; } + /** + * Returns all the registered plug-ins. + */ get plugins(): Array { return Array.from(this.#plugins.values()); } + /** + * Attach a new plug-in. + */ attachPlugin(plugin: AbstractProviderPlugin): this { if (this.#plugins.get(plugin.name)) { throw new Error(`cannot replace existing plugin: ${ plugin.name } `); @@ -397,10 +492,17 @@ export class AbstractProvider implements Provider { return this; } + /** + * Get a plugin by name. + */ getPlugin(name: string): null | T { return (this.#plugins.get(name)) || null; } + /** + * Prevent any CCIP-read operation, regardless of whether requested + * in a [[call]] using ``enableCcipRead``. + */ get disableCcipRead(): boolean { return this.#disableCcipRead; } set disableCcipRead(value: boolean) { this.#disableCcipRead = !!value; } @@ -424,6 +526,9 @@ export class AbstractProvider implements Provider { return await perform; } + /** + * Resolves to the data for executing the CCIP-read operations. + */ async ccipReadFetch(tx: PerformActionTransaction, calldata: string, urls: Array): Promise { if (this.disableCcipRead || urls.length === 0 || tx.to == null) { return null; } @@ -479,30 +584,60 @@ export class AbstractProvider implements Provider { }); } + /** + * Provides the opportunity for a sub-class to wrap a block before + * returning it, to add additional properties or an alternate + * sub-class of [[Block]]. + */ _wrapBlock(value: BlockParams, network: Network): Block { return new Block(formatBlock(value), this); } + /** + * Provides the opportunity for a sub-class to wrap a log before + * returning it, to add additional properties or an alternate + * sub-class of [[Log]]. + */ _wrapLog(value: LogParams, network: Network): Log { return new Log(formatLog(value), this); } + /** + * Provides the opportunity for a sub-class to wrap a transaction + * receipt before returning it, to add additional properties or an + * alternate sub-class of [[TransactionReceipt]]. + */ _wrapTransactionReceipt(value: TransactionReceiptParams, network: Network): TransactionReceipt { return new TransactionReceipt(formatTransactionReceipt(value), this); } + /** + * Provides the opportunity for a sub-class to wrap a transaction + * response before returning it, to add additional properties or an + * alternate sub-class of [[TransactionResponse]]. + */ _wrapTransactionResponse(tx: TransactionResponseParams, network: Network): TransactionResponse { return new TransactionResponse(formatTransactionResponse(tx), this); } + /** + * Resolves to the Network, forcing a network detection using whatever + * technique the sub-class requires. + * + * Sub-classes **must** override this. + */ _detectNetwork(): Promise { assert(false, "sub-classes must implement this", "UNSUPPORTED_OPERATION", { operation: "_detectNetwork" }); } - // Sub-classes should override this and handle PerformActionRequest requests, calling - // the super for any unhandled actions. + /** + * Sub-classes should use this to perform all built-in operations. All + * methods sanitizes and normalizes the values passed into this. + * + * Sub-classes **must** override this. + */ async _perform(req: PerformActionRequest): Promise { assert(false, `unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", { operation: req.method, @@ -511,16 +646,26 @@ export class AbstractProvider implements Provider { } // State + async getBlockNumber(): Promise { const blockNumber = getNumber(await this.#perform({ method: "getBlockNumber" }), "%response"); if (this.#lastBlockNumber >= 0) { this.#lastBlockNumber = blockNumber; } return blockNumber; } + /** + * Returns or resolves to the address for %%address%%, resolving ENS + * names and [[Addressable]] objects and returning if already an + * address. + */ _getAddress(address: AddressLike): string | Promise { return resolveAddress(address, this); } + /** + * Returns or resolves to a valid block tag for %%blockTag%%, resolving + * negative values and returning if already a valid block tag. + */ _getBlockTag(blockTag?: BlockTag): string | Promise { if (blockTag == null) { return "latest"; } @@ -550,6 +695,11 @@ export class AbstractProvider implements Provider { assertArgument(false, "invalid blockTag", "blockTag", blockTag); } + /** + * Returns or resolves to a filter for %%filter%%, resolving any ENS + * names or [[Addressable]] object and returning if already a valid + * filter. + */ _getFilter(filter: Filter | FilterByBlockHash): PerformActionFilter | Promise { // Create a canonical representation of the topics @@ -619,6 +769,11 @@ export class AbstractProvider implements Provider { return resolve(>address, fromBlock, toBlock); } + /** + * Returns or resovles to a transaction for %%request%%, resolving + * any ENS names or [[Addressable]] and returning if already a valid + * transaction. + */ _getTransactionRequest(_request: TransactionRequest): PerformActionTransaction | Promise { const request = copyRequest(_request); @@ -1059,6 +1214,9 @@ export class AbstractProvider implements Provider { }); } + /** + * Clear a timer created using the [[_setTimeout]] method. + */ _clearTimeout(timerId: number): void { const timer = this.#timers.get(timerId); if (!timer) { return; } @@ -1066,6 +1224,14 @@ export class AbstractProvider implements Provider { this.#timers.delete(timerId); } + /** + * Create a timer that will execute %%func%% after at least %%timeout%% + * (in ms). If %%timeout%% is unspecified, then %%func%% will execute + * in the next event loop. + * + * [Pausing](AbstractProvider-paused) the provider will pause any + * associated timers. + */ _setTimeout(_func: () => void, timeout?: number): number { if (timeout == null) { timeout = 0; } const timerId = this.#nextTimer++; @@ -1084,14 +1250,19 @@ export class AbstractProvider implements Provider { return timerId; } + /** + * Perform %%func%% on each subscriber. + */ _forEachSubscriber(func: (s: Subscriber) => void): void { for (const sub of this.#subs.values()) { func(sub.subscriber); } } - // Event API; sub-classes should override this; any supported - // event filter will have been munged into an EventFilter + /** + * Sub-classes may override this to customize subscription + * implementations. + */ _getSubscriber(sub: Subscription): Subscriber { switch (sub.type) { case "debug": @@ -1111,6 +1282,15 @@ export class AbstractProvider implements Provider { throw new Error(`unsupported event: ${ sub.type }`); } + /** + * If a [[Subscriber]] fails and needs to replace itself, this + * method may be used. + * + * For example, this is used for providers when using the + * ``eth_getFilterChanges`` method, which can return null if state + * filters are not supported by the backend, allowing the Subscriber + * to swap in a [[PollingEventSubscriber]]. + */ _recoverSubscriber(oldSub: Subscriber, newSub: Subscriber): void { for (const sub of this.#subs.values()) { if (sub.subscriber === oldSub) { @@ -1265,8 +1445,12 @@ export class AbstractProvider implements Provider { return this.off(event, listener); } - // Sub-classes should override this to shutdown any sockets, etc. - // but MUST call this super.shutdown. + /** + * Sub-classes may use this to shutdown any sockets or release their + * resources. + * + * Sub-classes **must** call ``super.destroy()``. + */ destroy(): void { // Stop all listeners this.removeAllListeners(); @@ -1277,6 +1461,17 @@ export class AbstractProvider implements Provider { } } + /** + * Whether the provider is currently paused. + * + * A paused provider will not emit any events, and generally should + * not make any requests to the network, but that is up to sub-classes + * to manage. + * + * Setting ``paused = true`` is identical to calling ``.pause(false)``, + * which will buffer any events that occur while paused until the + * provider is unpaused. + */ get paused(): boolean { return (this.#pausedState != null); } set paused(pause: boolean) { if (!!pause === this.paused) { return; } @@ -1288,6 +1483,11 @@ export class AbstractProvider implements Provider { } } + /** + * Pause the provider. If %%dropWhilePaused%%, any events that occur + * while paused are dropped, otherwise all events will be emitted once + * the provider is unpaused. + */ pause(dropWhilePaused?: boolean): void { this.#lastBlockNumber = -1; @@ -1310,6 +1510,9 @@ export class AbstractProvider implements Provider { } } + /** + * Resume the provider. + */ resume(): void { if (this.#pausedState == null) { return; } diff --git a/src.ts/providers/abstract-signer.ts b/src.ts/providers/abstract-signer.ts index 3dfb4a7c7..afd9aa223 100644 --- a/src.ts/providers/abstract-signer.ts +++ b/src.ts/providers/abstract-signer.ts @@ -1,5 +1,7 @@ /** - * About Abstract Signer and subclassing + * Generally the [[Wallet]] and [[JsonRpcSigner]] and their sub-classes + * are sufficent for most developers, but this is provided to + * fascilitate more complex Signers. * * @_section: api/providers/abstract-signer: Subclassing Signer [abstract-signer] */ @@ -49,14 +51,36 @@ async function populate(signer: AbstractSigner, tx: TransactionRequest): Promise } +/** + * An **AbstractSigner** includes most of teh functionality required + * to get a [[Signer]] working as expected, but requires a few + * Signer-specific methods be overridden. + * + */ export abstract class AbstractSigner

implements Signer { + /** + * The provider this signer is connected to. + */ readonly provider!: P; + /** + * Creates a new Signer connected to %%provider%%. + */ constructor(provider?: P) { defineProperties(this, { provider: (provider || null) }); } + /** + * Resolves to the Signer address. + */ abstract getAddress(): Promise; + + /** + * Returns the signer connected to %%provider%%. + * + * This may throw, for example, a Signer connected over a Socket or + * to a specific instance of a node may not be transferrable. + */ abstract connect(provider: null | Provider): Signer; async getNonce(blockTag?: BlockTag): Promise { @@ -216,9 +240,24 @@ export abstract class AbstractSigner

>, value: Record): Promise; } +/** + * A **VoidSigner** is a class deisgned to allow an address to be used + * in any API which accepts a Signer, but for which there are no + * credentials available to perform any actual signing. + * + * This for example allow impersonating an account for the purpose of + * static calls or estimating gas, but does not allow sending transactions. + */ export class VoidSigner extends AbstractSigner { + /** + * The signer address. + */ readonly address!: string; + /** + * Creates a new **VoidSigner** with %%address%% attached to + * %%provider%%. + */ constructor(address: string, provider?: null | Provider) { super(provider); defineProperties(this, { address }); diff --git a/src.ts/providers/contracts.ts b/src.ts/providers/contracts.ts index 88c56ede1..abb6d2b87 100644 --- a/src.ts/providers/contracts.ts +++ b/src.ts/providers/contracts.ts @@ -2,20 +2,41 @@ import type { Provider, TransactionRequest, TransactionResponse } from "./provider.js"; -// The object that will be used to run Contracts. The Signer and Provider -// both adhere to this, but other types of objects may wish to as well. +/** + * A **ContractRunner** is a generic interface which defines an object + * capable of interacting with a Contract on the network. + * + * The more operations supported, the more utility it is capable of. + * + * The most common ContractRunners are [Providers](Provider) which enable + * read-only access and [Signers](Signer) which enable write-access. + */ export interface ContractRunner { + /** + * The provider used for necessary state querying operations. + * + * This can also point to the **ContractRunner** itself, in the + * case of an [[AbstractProvider]]. + */ provider: null | Provider; - // Required to estimate gas; usually a Signer or Provider + /** + * Required to estimate gas. + */ estimateGas?: (tx: TransactionRequest) => Promise; - // Required for pure, view or static calls to contracts; usually a Signer or Provider + /** + * Required for pure, view or static calls to contracts. + */ call?: (tx: TransactionRequest) => Promise; - // Required to support ENS names; usually a Signer or Provider + /** + * Required to support ENS names + */ resolveName?: (name: string) => Promise; - // Required for mutating calls; usually a Signer + /** + * Required for state mutating calls + */ sendTransaction?: (tx: TransactionRequest) => Promise; } diff --git a/src.ts/providers/ens-resolver.ts b/src.ts/providers/ens-resolver.ts index dc890c126..b9498fd47 100644 --- a/src.ts/providers/ens-resolver.ts +++ b/src.ts/providers/ens-resolver.ts @@ -1,5 +1,6 @@ /** - * About ENS Resolver + * ENS is a service which allows easy-to-remember names to map to + * network addresses. * * @_section: api/providers/ens-resolver:ENS Resolver [about-ens-rsolver] */ @@ -50,7 +51,14 @@ export type AvatarLinkageType = "name" | "avatar" | "!avatar" | "url" | "data" | * An individual record for each step during avatar resolution. */ export interface AvatarLinkage { + /** + * The type of linkage. + */ type: AvatarLinkageType; + + /** + * The linkage value. + */ value: string; }; @@ -63,7 +71,17 @@ export interface AvatarLinkage { * each completed step during avatar resolution. */ export interface AvatarResult { + /** + * How the [[url]] was arrived at, resolving the many steps required + * for an avatar URL. + */ linkage: Array; + + /** + * The avatar URL or null if the avatar was not set, or there was + * an issue during validation (such as the address not owning the + * avatar or a metadata error). + */ url: null | string; }; @@ -71,8 +89,14 @@ export interface AvatarResult { * A provider plugin super-class for processing multicoin address types. */ export abstract class MulticoinProviderPlugin implements AbstractProviderPlugin { + /** + * The name. + */ readonly name!: string; + /** + * Creates a new **MulticoinProviderPluing** for %%name%%. + */ constructor(name: string) { defineProperties(this, { name }); } @@ -81,14 +105,23 @@ export abstract class MulticoinProviderPlugin implements AbstractProviderPlugin return this; } + /** + * Returns ``true`` if %%coinType%% is supported by this plugin. + */ supportsCoinType(coinType: number): boolean { return false; } + /** + * Resovles to the encoded %%address%% for %%coinType%%. + */ async encodeAddress(coinType: number, address: string): Promise { throw new Error("unsupported coin"); } + /** + * Resovles to the decoded %%data%% for %%coinType%%. + */ async decodeAddress(coinType: number, data: BytesLike): Promise { throw new Error("unsupported coin"); } @@ -97,9 +130,14 @@ export abstract class MulticoinProviderPlugin implements AbstractProviderPlugin const BasicMulticoinPluginId = "org.ethers.plugins.provider.BasicMulticoin"; /** - * A basic multicoin provider plugin. + * A **BasicMulticoinProviderPlugin** provides service for common + * coin types, which do not require additional libraries to encode or + * decode. */ export class BasicMulticoinProviderPlugin extends MulticoinProviderPlugin { + /** + * Creates a new **BasicMulticoinProviderPlugin**. + */ constructor() { super(BasicMulticoinPluginId); } diff --git a/src.ts/providers/formatting.ts b/src.ts/providers/formatting.ts index 1fb567e5b..f41527bc7 100644 --- a/src.ts/providers/formatting.ts +++ b/src.ts/providers/formatting.ts @@ -7,86 +7,78 @@ import type { Signature } from "../crypto/index.js"; import type { AccessList } from "../transaction/index.js"; -/* -export interface TransactionRequest { - type?: null | number; - - to?: null | AddressLike; - from?: null | AddressLike; - - nonce?: null | number; - - gasLimit?: null | BigNumberish; - gasPrice?: null | BigNumberish; - - maxPriorityFeePerGas?: null | BigNumberish; - maxFeePerGas?: null | BigNumberish; - - data?: null | string; - value?: null | BigNumberish; - chainId?: null | BigNumberish; - - accessList?: null | AccessListish; - - customData?: any; - - // Only meaningful when used for call - blockTag?: BlockTag; - enableCcipRead?: boolean; - - // Todo? - //gasMultiplier?: number; -}; -export interface PreparedTransactionRequest { - type?: number; - - to?: AddressLike; - from?: AddressLike; - - nonce?: number; - - gasLimit?: bigint; - gasPrice?: bigint; - - maxPriorityFeePerGas?: bigint; - maxFeePerGas?: bigint; - - data?: string; - value?: bigint; - chainId?: bigint; - - accessList?: AccessList; - - customData?: any; - - blockTag?: BlockTag; - enableCcipRead?: boolean; -} -*/ - ////////////////////// // Block +/** + * a **BlockParams** encodes the minimal required properties for a + * formatted block. + */ export interface BlockParams { + /** + * The block hash. + */ hash?: null | string; + /** + * The block number. + */ number: number; + + /** + * The timestamp for this block, which is the number of seconds + * since epoch that this block was included. + */ timestamp: number; + /** + * The hash of the previous block in the blockchain. The genesis block + * has the parentHash of the [[ZeroHash]]. + */ parentHash: string; + /** + * A random sequence provided during the mining process for + * proof-of-work networks. + */ nonce: string; + + /** + * For proof-of-work networks, the difficulty target is used to + * adjust the difficulty in mining to ensure a expected block rate. + */ difficulty: bigint; + /** + * The maximum amount of gas a block can consume. + */ gasLimit: bigint; + + /** + * The amount of gas a block consumed. + */ gasUsed: bigint; + /** + * The miner (or author) of a block. + */ miner: string; + + /** + * Additional data the miner choose to include. + */ extraData: string; + /** + * The protocol-defined base fee per gas in an [[link-eip-1559]] + * block. + */ baseFeePerGas: null | bigint; + /** + * The list of transactions in the block. + */ transactions: ReadonlyArray; }; @@ -94,19 +86,57 @@ export interface BlockParams { ////////////////////// // Log +/** + * a **LogParams** encodes the minimal required properties for a + * formatted log. + */ export interface LogParams { + /** + * The transaction hash for the transaxction the log occurred in. + */ transactionHash: string; + + /** + * The block hash of the block that included the transaction for this + * log. + */ blockHash: string; + + /** + * The block number of the block that included the transaction for this + * log. + */ blockNumber: number; + /** + * Whether this log was removed due to the transaction it was included + * in being removed dur to an orphaned block. + */ removed: boolean; + /** + * The address of the contract that emitted this log. + */ address: string; + + /** + * The data emitted with this log. + */ data: string; + /** + * The topics emitted with this log. + */ topics: ReadonlyArray; + /** + * The index of this log. + */ index: number; + + /** + * The transaction index of this log. + */ transactionIndex: number; } @@ -114,28 +144,99 @@ export interface LogParams { ////////////////////// // Transaction Receipt +/** + * a **TransactionReceiptParams** encodes the minimal required properties + * for a formatted transaction receipt. + */ export interface TransactionReceiptParams { + /** + * The target of the transaction. If null, the transaction was trying + * to deploy a transaction with the ``data`` as the initi=code. + */ to: null | string; + + /** + * The sender of the transaction. + */ from: string; + + /** + * If the transaction was directly deploying a contract, the [[to]] + * will be null, the ``data`` will be initcode and if successful, this + * will be the address of the contract deployed. + */ contractAddress: null | string; + /** + * The transaction hash. + */ hash: string; + + /** + * The transaction index. + */ index: number; + /** + * The block hash of the block that included this transaction. + */ blockHash: string; + + /** + * The block number of the block that included this transaction. + */ blockNumber: number; + /** + * The bloom filter for the logs emitted during execution of this + * transaction. + */ logsBloom: string; + + /** + * The logs emitted during the execution of this transaction. + */ logs: ReadonlyArray; + /** + * The amount of gas consumed executing this transaciton. + */ gasUsed: bigint; + + /** + * The total amount of gas consumed during the entire block up to + * and including this transaction. + */ cumulativeGasUsed: bigint; + + /** + * The actual gas price per gas charged for this transaction. + */ gasPrice?: null | bigint; + + /** + * The actual gas price per gas charged for this transaction. + */ effectiveGasPrice?: null | bigint; + /** + * The [[link-eip-2718]] envelope type. + */ type: number; //byzantium: boolean; + + /** + * The status of the transaction execution. If ``1`` then the + * the transaction returned success, if ``0`` then the transaction + * was reverted. For pre-byzantium blocks, this is usually null, but + * some nodes may have backfilled this data. + */ status: null | number; + + /** + * The root of this transaction in a pre-bazatium block. In + * post-byzantium blocks this is null. + */ root: null | string; } @@ -158,33 +259,97 @@ export interface ByzantiumTransactionReceipt { ////////////////////// // Transaction Response +/** + * a **TransactionResponseParams** encodes the minimal required properties + * for a formatted transaction response. + */ export interface TransactionResponseParams { + /** + * The block number of the block that included this transaction. + */ blockNumber: null | number; + + /** + * The block hash of the block that included this transaction. + */ blockHash: null | string; + /** + * The transaction hash. + */ hash: string; + + /** + * The transaction index. + */ index: number; + /** + * The [[link-eip-2718]] transaction type. + */ type: number; + /** + * The target of the transaction. If ``null``, the ``data`` is initcode + * and this transaction is a deployment transaction. + */ to: null | string; + + /** + * The sender of the transaction. + */ from: string; + /** + * The nonce of the transaction, used for replay protection. + */ nonce: number; + /** + * The maximum amount of gas this transaction is authorized to consume. + */ gasLimit: bigint; + /** + * For legacy transactions, this is the gas price per gas to pay. + */ gasPrice: bigint; + /** + * For [[link-eip-1559]] transactions, this is the maximum priority + * fee to allow a producer to claim. + */ maxPriorityFeePerGas: null | bigint; + + /** + * For [[link-eip-1559]] transactions, this is the maximum fee that + * will be paid. + */ maxFeePerGas: null | bigint; + /** + * The transaction data. + */ data: string; + + /** + * The transaction value (in wei). + */ value: bigint; + + /** + * The chain ID this transaction is valid on. + */ chainId: bigint; + /** + * The signature of the transaction. + */ signature: Signature; + /** + * The transaction access list. + */ accessList: null | AccessList; }; diff --git a/src.ts/providers/index.ts b/src.ts/providers/index.ts index c1e206839..c8f27a0e0 100644 --- a/src.ts/providers/index.ts +++ b/src.ts/providers/index.ts @@ -1,11 +1,16 @@ /** - * About providers. + * A **Provider** provides a connection to the blockchain, whch can be + * used to query its current state, simulate execution and send transactions + * to update the state. + * + * It is one of the most fundamental components of interacting with a + * blockchain application, and there are many ways to connect, such as over + * HTTP, WebSockets or injected providers such as [MetaMask](link-metamask). * * @_section: api/providers:Providers [about-providers] */ - export { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js"; diff --git a/src.ts/providers/network.ts b/src.ts/providers/network.ts index 7e01a508f..552d5eeea 100644 --- a/src.ts/providers/network.ts +++ b/src.ts/providers/network.ts @@ -1,5 +1,6 @@ /** - * About networks + * A **Network** encapsulates the various properties required to + * interact with a specific chain. * * @_subsection: api/providers:Networks [networks] */ @@ -91,18 +92,28 @@ const Networks: Map Network> = new Map(); // @TODO: Add a _ethersNetworkObj variable to better detect network ovjects +/** + * A **Network** provides access to a chain's properties and allows + * for plug-ins to extend functionality. + */ export class Network { #name: string; #chainId: bigint; #plugins: Map; + /** + * Creates a new **Network** for %%name%% and %%chainId%%. + */ constructor(name: string, chainId: BigNumberish) { this.#name = name; this.#chainId = getBigInt(chainId); this.#plugins = new Map(); } + /** + * Returns a JSON-compatible representation of a Network. + */ toJSON(): any { return { name: this.name, chainId: String(this.chainId) }; } diff --git a/src.ts/providers/plugins-network.ts b/src.ts/providers/plugins-network.ts index aba8b8ad8..8a42ec17b 100644 --- a/src.ts/providers/plugins-network.ts +++ b/src.ts/providers/plugins-network.ts @@ -10,13 +10,28 @@ import type { const EnsAddress = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; +/** + * A **NetworkPlugin** provides additional functionality on a [[Network]]. + */ export class NetworkPlugin { + /** + * The name of the plugin. + * + * It is recommended to use reverse-domain-notation, which permits + * unique names with a known authority as well as hierarchal entries. + */ readonly name!: string; + /** + * Creates a new **NetworkPlugin**. + */ constructor(name: string) { defineProperties(this, { name }); } + /** + * Creates a copy of this plugin. + */ clone(): NetworkPlugin { return new NetworkPlugin(this.name); } @@ -26,28 +41,91 @@ export class NetworkPlugin { // } } -// Networks can use this plugin to override calculations for the -// intrinsic gas cost of a transaction for networks that differ -// from the latest hardfork on Ethereum mainnet. + +/** + * The gas cost parameters for a [[GasCostPlugin]]. + */ export type GasCostParameters = { + /** + * The transactions base fee. + */ txBase?: number; + + /** + * The fee for creating a new account. + */ txCreate?: number; + + /** + * The fee per zero-byte in the data. + */ txDataZero?: number; + + /** + * The fee per non-zero-byte in the data. + */ txDataNonzero?: number; + + /** + * The fee per storage key in the [[link-eip-2930]] access list. + */ txAccessListStorageKey?: number; + + /** + * The fee per address in the [[link-eip-2930]] access list. + */ txAccessListAddress?: number; }; +/** + * A **GasCostPlugin** allows a network to provide alternative values when + * computing the intrinsic gas required for a transaction. + */ export class GasCostPlugin extends NetworkPlugin implements GasCostParameters { + /** + * The block number to treat these values as valid from. + * + * This allows a hardfork to have updated values included as well as + * mulutiple hardforks to be supported. + */ readonly effectiveBlock!: number; + /** + * The transactions base fee. + */ readonly txBase!: number; + + /** + * The fee for creating a new account. + */ readonly txCreate!: number; + + /** + * The fee per zero-byte in the data. + */ readonly txDataZero!: number; + + /** + * The fee per non-zero-byte in the data. + */ readonly txDataNonzero!: number; + + /** + * The fee per storage key in the [[link-eip-2930]] access list. + */ readonly txAccessListStorageKey!: number; + + /** + * The fee per address in the [[link-eip-2930]] access list. + */ readonly txAccessListAddress!: number; + + /** + * Creates a new GasCostPlugin from %%effectiveBlock%% until the + * latest block or another GasCostPlugin supercedes that block number, + * with the associated %%costs%%. + */ constructor(effectiveBlock?: number, costs?: GasCostParameters) { if (effectiveBlock == null) { effectiveBlock = 0; } super(`org.ethers.network.plugins.GasCost#${ (effectiveBlock || 0) }`); @@ -75,16 +153,32 @@ export class GasCostPlugin extends NetworkPlugin implements GasCostParameters { } } -// Networks shoudl use this plugin to specify the contract address -// and network necessary to resolve ENS names. +/** + * An **EnsPlugin** allows a [[Network]] to specify the ENS Registry + * Contract address and the target network to use when using that + * contract. + * + * Various testnets have their own instance of the contract to use, but + * in general, the mainnet instance supports multi-chain addresses and + * should be used. + */ export class EnsPlugin extends NetworkPlugin { - // The ENS contract address + /** + * The ENS Registrty Contract address. + */ readonly address!: string; - // The network ID that the ENS contract lives on + /** + * The chain ID that the ENS contract lives on. + */ readonly targetNetwork!: number; + /** + * Creates a new **EnsPlugin** connected to %%address%% on the + * %%targetNetwork%%. The default ENS address and mainnet is used + * if unspecified. + */ constructor(address?: null | string, targetNetwork?: null | number) { super("org.ethers.plugins.network.Ens"); defineProperties(this, { @@ -98,18 +192,34 @@ export class EnsPlugin extends NetworkPlugin { } } +/** + * A **FeeDataNetworkPlugin** allows a network to provide and alternate + * means to specify its fee data. + * + * For example, a network which does not support [[link-eip-1559]] may + * choose to use a Gas Station site to approximate the gas price. + */ export class FeeDataNetworkPlugin extends NetworkPlugin { readonly #feeDataFunc: (provider: Provider) => Promise; + /** + * The fee data function provided to the constructor. + */ get feeDataFunc(): (provider: Provider) => Promise { return this.#feeDataFunc; } + /** + * Creates a new **FeeDataNetworkPlugin**. + */ constructor(feeDataFunc: (provider: Provider) => Promise) { super("org.ethers.plugins.network.FeeData"); this.#feeDataFunc = feeDataFunc; } + /** + * Resolves to the fee data. + */ async getFeeData(provider: Provider): Promise { return await this.#feeDataFunc(provider); } diff --git a/src.ts/providers/provider-browser.ts b/src.ts/providers/provider-browser.ts index f474df3d8..6f95a6033 100644 --- a/src.ts/providers/provider-browser.ts +++ b/src.ts/providers/provider-browser.ts @@ -8,11 +8,22 @@ import type { } from "./provider-jsonrpc.js"; import type { Networkish } from "./network.js"; - +/** + * The interface to an [[link-eip-1193]] provider, which is a standard + * used by most injected providers, which the [[BrowserProvider]] accepts + * and exposes the API of. + */ export interface Eip1193Provider { + /** + * See [[link-eip-1193]] for details on this method. + */ request(request: { method: string, params?: Array | Record }): Promise; }; +/** + * The possible additional events dispatched when using the ``"debug"`` + * event on a [[BrowserProvider]]. + */ export type DebugEventBrowserProvider = { action: "sendEip1193Payload", payload: { method: string, params: Array } @@ -25,10 +36,18 @@ export type DebugEventBrowserProvider = { }; - +/** + * A **BrowserProvider** is intended to wrap an injected provider which + * adheres to the [[link-eip-1193]] standard, which most (if not all) + * currently do. + */ export class BrowserProvider extends JsonRpcApiPollingProvider { #request: (method: string, params: Array | Record) => Promise; + /** + * Connnect to the %%ethereum%% provider, optionally forcing the + * %%network%%. + */ constructor(ethereum: Eip1193Provider, network?: Networkish) { super(network, { batchMaxCount: 1 }); @@ -88,6 +107,9 @@ export class BrowserProvider extends JsonRpcApiPollingProvider { return super.getRpcError(payload, error); } + /** + * Resolves to ``true`` if the provider manages the %%address%%. + */ async hasSigner(address: number | string): Promise { if (address == null) { address = 0; } diff --git a/src.ts/providers/provider-fallback.ts b/src.ts/providers/provider-fallback.ts index 624c8a4e0..204e74406 100644 --- a/src.ts/providers/provider-fallback.ts +++ b/src.ts/providers/provider-fallback.ts @@ -1,5 +1,6 @@ /** - * Explain all the nitty-gritty about the **FallbackProvider**. + * A **FallbackProvider** providers resiliance, security and performatnce + * in a way that is customizable and configurable. * * @_section: api/providers/fallback-provider:Fallback Provider [about-fallback-provider] */ @@ -45,17 +46,27 @@ function stringify(value: any): string { */ export interface FallbackProviderConfig { - // The provider + /** + * The provider. + */ provider: AbstractProvider; - // How long to wait for a response before getting impatient - // and ispatching the next provider + /** + * The amount of time to wait before kicking off the next provider. + * + * Any providers that have not responded can still respond and be + * counted, but this ensures new providers start. + */ stallTimeout?: number; - // Lower values are dispatched first + /** + * The priority. Lower priority providers are dispatched first. + */ priority?: number; - // How much this provider contributes to the quorum + /** + * The amount of weight a provider is given against the quorum. + */ weight?: number; }; @@ -68,28 +79,44 @@ const defaultConfig = { stallTimeout: 400, priority: 1, weight: 1 }; */ export interface FallbackProviderState extends Required { - // The most recent blockNumber this provider has reported (-2 if none) + /** + * The most recent blockNumber this provider has reported (-2 if none). + */ blockNumber: number; - // The number of total requests ever sent to this provider + /** + * The number of total requests ever sent to this provider. + */ requests: number; - // The number of responses that errored + /** + * The number of responses that errored. + */ errorResponses: number; - // The number of responses that occured after the result resolved + /** + * The number of responses that occured after the result resolved. + */ lateResponses: number; - // How many times syncing was required to catch up the expected block + /** + * How many times syncing was required to catch up the expected block. + */ outOfSync: number; - // The number of requests which reported unsupported operation + /** + * The number of requests which reported unsupported operation. + */ unsupportedEvents: number; - // A rolling average (5% current duration) for response time + /** + * A rolling average (5% current duration) for response time. + */ rollingDuration: number; - // The ratio of quorum-agreed results to total + /** + * The ratio of quorum-agreed results to total. + */ score: number; } @@ -317,13 +344,28 @@ function getFuzzyMode(quorum: number, results: Array): undefined | } /** - * A Fallback Provider. + * A **FallbackProvider** manages several [[Providers]] providing + * resiliance by switching between slow or misbehaving nodes, security + * by requiring multiple backends to aggree and performance by allowing + * faster backends to respond earlier. * */ export class FallbackProvider extends AbstractProvider { + /** + * The number of backends that must agree on a value before it is + * accpeted. + */ readonly quorum: number; + + /** + * @_ignore: + */ readonly eventQuorum: number; + + /** + * @_ignore: + */ readonly eventWorkers: number; readonly #configs: Array; @@ -331,6 +373,13 @@ export class FallbackProvider extends AbstractProvider { #height: number; #initialSyncPromise: null | Promise; + /** + * Creates a new **FallbackProvider** with %%providers%% connected to + * %%network%%. + * + * If a [[Provider]] is included in %%providers%%, defaults are used + * for the configuration. + */ constructor(providers: Array, network?: Networkish) { super(network); this.#configs = providers.map((p) => { @@ -371,6 +420,9 @@ export class FallbackProvider extends AbstractProvider { // throw new Error("@TODO"); //} + /** + * Transforms a %%req%% into the correct method call on %%provider%%. + */ async _translatePerform(provider: AbstractProvider, req: PerformActionRequest): Promise { switch (req.method) { case "broadcastTransaction": diff --git a/src.ts/providers/provider-ipcsocket.ts b/src.ts/providers/provider-ipcsocket.ts index ec50645af..6226f658b 100644 --- a/src.ts/providers/provider-ipcsocket.ts +++ b/src.ts/providers/provider-ipcsocket.ts @@ -22,8 +22,17 @@ function splitBuffer(data: Buffer): { messages: Array, remaining: Buffer return { messages, remaining: data.subarray(lastStart) }; } +/** + * An **IpcSocketProvider** connects over an IPC socket on the host + * which provides fast access to the node, but requires the node and + * the script run on the same machine. + */ export class IpcSocketProvider extends SocketProvider { #socket: Socket; + + /** + * The connected socket. + */ get socket(): Socket { return this.#socket; } constructor(path: string, network?: Networkish) { diff --git a/src.ts/providers/provider-jsonrpc.ts b/src.ts/providers/provider-jsonrpc.ts index d771803a3..bdcf7d9be 100644 --- a/src.ts/providers/provider-jsonrpc.ts +++ b/src.ts/providers/provider-jsonrpc.ts @@ -1,5 +1,11 @@ /** - * About JSON-RPC... + * One of the most common ways to interact with the blockchain is + * by a node running a JSON-RPC interface which can be connected to, + * based on the transport, using: + * + * - HTTP or HTTPS - [[JsonRpcProvider]] + * - WebSocket - [[WebSocketProvider]] + * - IPC - [[IpcSocketProvider]] * * @_section: api/providers/jsonrpc:JSON-RPC Provider [about-jsonrpcProvider] */ @@ -81,9 +87,24 @@ function isPollable(value: any): value is Pollable { * A JSON-RPC payload, which are sent to a JSON-RPC server. */ export type JsonRpcPayload = { + /** + * The JSON-RPC request ID. + */ id: number; + + /** + * The JSON-RPC request method. + */ method: string; + + /** + * The JSON-RPC request parameters. + */ params: Array | Record; + + /** + * A required constant in the JSON-RPC specification. + */ jsonrpc: "2.0"; }; @@ -91,7 +112,14 @@ export type JsonRpcPayload = { * A JSON-RPC result, which are returned on success from a JSON-RPC server. */ export type JsonRpcResult = { + /** + * The response ID to match it to the relevant request. + */ id: number; + + /** + * The response result. + */ result: any; }; @@ -99,7 +127,14 @@ export type JsonRpcResult = { * A JSON-RPC error, which are returned on failure from a JSON-RPC server. */ export type JsonRpcError = { + /** + * The response ID to match it to the relevant request. + */ id: number; + + /** + * The response error. + */ error: { code: number; message?: string; @@ -168,22 +203,72 @@ const defaultOptions = { batchMaxCount: 100 // 100 requests } +/** + * A **JsonRpcTransactionRequest** is formatted as needed by the JSON-RPC + * Ethereum API specification. + */ export interface JsonRpcTransactionRequest { + /** + * The sender address to use when signing. + */ from?: string; + + /** + * The target address. + */ to?: string; + + /** + * The transaction data. + */ data?: string; + /** + * The chain ID the transaction is valid on. + */ chainId?: string; + + /** + * The [[link-eip-2718]] transaction type. + */ type?: string; + + /** + * The maximum amount of gas to allow a transaction to consume. + * + * In most other places in ethers, this is called ``gasLimit`` which + * differs from the JSON-RPC Ethereum API specification. + */ gas?: string; + /** + * The gas price per wei for transactions prior to [[link-eip-1559]]. + */ gasPrice?: string; + + /** + * The maximum fee per gas for [[link-eip-1559]] transactions. + */ maxFeePerGas?: string; + + /** + * The maximum priority fee per gas for [[link-eip-1559]] transactions. + */ maxPriorityFeePerGas?: string; + /** + * The nonce for the transaction. + */ nonce?: string; + + /** + * The transaction value (in wei). + */ value?: string; + /** + * The transaction access list. + */ accessList?: Array<{ address: string, storageKeys: Array }>; } diff --git a/src.ts/providers/provider-socket.ts b/src.ts/providers/provider-socket.ts index cb07e2db5..34153fe62 100644 --- a/src.ts/providers/provider-socket.ts +++ b/src.ts/providers/provider-socket.ts @@ -27,10 +27,18 @@ type JsonRpcSubscription = { } }; +/** + * A **SocketSubscriber** uses a socket transport to handle events and + * should use [[_emit]] to manage the events. + */ export class SocketSubscriber implements Subscriber { #provider: SocketProvider; #filter: string; + + /** + * The filter. + */ get filter(): Array { return JSON.parse(this.#filter); } #filterId: null | Promise; @@ -38,6 +46,10 @@ export class SocketSubscriber implements Subscriber { #emitPromise: null | Promise; + /** + * Creates a new **SocketSubscriber** attached to %%provider%% listening + * to %%filter%%. + */ constructor(provider: SocketProvider, filter: Array) { this.#provider = provider; this.#filter = JSON.stringify(filter); @@ -72,6 +84,9 @@ export class SocketSubscriber implements Subscriber { this.#paused = null; } + /** + * @_ignore: + */ _handleMessage(message: any): void { if (this.#filterId == null) { return; } if (this.#paused === null) { @@ -91,12 +106,23 @@ export class SocketSubscriber implements Subscriber { } } + /** + * Sub-classes **must** override this to emit the events on the + * provider. + */ async _emit(provider: SocketProvider, message: any): Promise { throw new Error("sub-classes must implemente this; _emit"); } } +/** + * A **SocketBlockSubscriber** listens for ``newHeads`` events and emits + * ``"block"`` events. + */ export class SocketBlockSubscriber extends SocketSubscriber { + /** + * @_ignore: + */ constructor(provider: SocketProvider) { super(provider, [ "newHeads" ]); } @@ -106,7 +132,15 @@ export class SocketBlockSubscriber extends SocketSubscriber { } } +/** + * A **SocketPendingSubscriber** listens for pending transacitons and emits + * ``"pending"`` events. + */ export class SocketPendingSubscriber extends SocketSubscriber { + + /** + * @_ignore: + */ constructor(provider: SocketProvider) { super(provider, [ "newPendingTransactions" ]); } @@ -116,10 +150,20 @@ export class SocketPendingSubscriber extends SocketSubscriber { } } +/** + * A **SocketEventSubscriber** listens for event logs. + */ export class SocketEventSubscriber extends SocketSubscriber { #logFilter: string; + + /** + * The filter. + */ get logFilter(): EventFilter { return JSON.parse(this.#logFilter); } + /** + * @_ignore: + */ constructor(provider: SocketProvider, filter: EventFilter) { super(provider, [ "logs", filter ]); this.#logFilter = JSON.stringify(filter); @@ -131,8 +175,9 @@ export class SocketEventSubscriber extends SocketSubscriber { } /** - * SocketProvider... - * + * A **SocketProvider** is backed by a long-lived connection over a + * socket, which can subscribe and receive real-time messages over + * its communication channel. */ export class SocketProvider extends JsonRpcApiProvider { #callbacks: Map void, reject: (e: Error) => void }>; @@ -144,6 +189,11 @@ export class SocketProvider extends JsonRpcApiProvider { // registering, queue them #pending: Map>; + /** + * Creates a new **SocketProvider** connected to %%network%%. + * + * If unspecified, the network will be discovered. + */ constructor(network?: Networkish) { super(network, { batchMaxCount: 1 }); this.#callbacks = new Map(); @@ -181,6 +231,10 @@ export class SocketProvider extends JsonRpcApiProvider { return super._getSubscriber(sub); } + /** + * Register a new subscriber. This is used internalled by Subscribers + * and generally is unecessary unless extending capabilities. + */ _register(filterId: number | string, subscriber: SocketSubscriber): void { this.#subs.set(filterId, subscriber); const pending = this.#pending.get(filterId); @@ -227,7 +281,10 @@ export class SocketProvider extends JsonRpcApiProvider { } */ - // Sub-classes must call this for each message + /** + * Sub-classes **must** call this with messages received over their + * transport to be processed and dispatched. + */ async _processMessage(message: string): Promise { const result = (JSON.parse(message)); @@ -267,6 +324,10 @@ export class SocketProvider extends JsonRpcApiProvider { } } + /** + * Sub-classes **must** override this to send %%message%% over their + * transport. + */ async _write(message: string): Promise { throw new Error("sub-classes must override this"); } diff --git a/src.ts/providers/provider-websocket.ts b/src.ts/providers/provider-websocket.ts index fe96f8432..ea2ba5175 100644 --- a/src.ts/providers/provider-websocket.ts +++ b/src.ts/providers/provider-websocket.ts @@ -6,6 +6,9 @@ import { SocketProvider } from "./provider-socket.js"; import type { Networkish } from "./network.js"; +/** + * A generic interface to a Websocket-like object. + */ export interface WebSocketLike { onopen: null | ((...args: Array) => any); onmessage: null | ((...args: Array) => any); @@ -17,8 +20,22 @@ export interface WebSocketLike { close(code?: number, reason?: string): void; } +/** + * A function which can be used to re-create a WebSocket connection + * on disconnect. + */ export type WebSocketCreator = () => WebSocketLike; +/** + * A JSON-RPC provider which is backed by a WebSocket. + * + * WebSockets are often preferred because they retain a live connection + * to a server, which permits more instant access to events. + * + * However, this incurs higher server infrasturture costs, so additional + * resources may be required to host your own WebSocket nodes and many + * third-party services charge additional fees for WebSocket endpoints. + */ export class WebSocketProvider extends SocketProvider { #connect: null | WebSocketCreator; diff --git a/src.ts/providers/provider.ts b/src.ts/providers/provider.ts index c35ba79ad..915234189 100644 --- a/src.ts/providers/provider.ts +++ b/src.ts/providers/provider.ts @@ -16,6 +16,19 @@ import type { Network } from "./network.js"; const BN_0 = BigInt(0); +/** + * A **BlockTag** specifies a specific block. + * + * **numeric value** - specifies the block height, where + * the genesis block is block 0; many operations accept a negative + * value which indicates the block number should be deducted from + * the most recent block. A numeric value may be a ``number``, ``bigint``, + * or a decimal of hex string. + * + * **blockhash** - specifies a specific block by its blockhash; this allows + * potentially orphaned blocks to be specifed, without ambiguity, but many + * backends do not support this for some operations. + */ export type BlockTag = BigNumberish | string; import { @@ -101,62 +114,215 @@ export class FeeData { } +/** + * A **TransactionRequest** is a transactions with potentially various + * properties not defined, or with less strict types for its values. + * + * This is used to pass to various operations, which will internally + * coerce any types and populate any necessary values. + */ export interface TransactionRequest { + /** + * The transaction type. + */ type?: null | number; + /** + * The target of the transaction. + */ to?: null | AddressLike; + + /** + * The sender of the transaction. + */ from?: null | AddressLike; + /** + * The nonce of the transaction, used to prevent replay attacks. + */ nonce?: null | number; + /** + * The maximum amount of gas to allow this transaction to consime. + */ gasLimit?: null | BigNumberish; + + /** + * The gas price to use for legacy transactions or transactions on + * legacy networks. + * + * Most of the time the ``max*FeePerGas`` is preferred. + */ gasPrice?: null | BigNumberish; + /** + * The [[link-eip-1559]] maximum priority fee to pay per gas. + */ maxPriorityFeePerGas?: null | BigNumberish; + + /** + * The [[link-eip-1559]] maximum total fee to pay per gas. The actual + * value used is protocol enforced to be the block's base fee. + */ maxFeePerGas?: null | BigNumberish; + /** + * The transaction data. + */ data?: null | string; + + /** + * The transaction value (in wei). + */ value?: null | BigNumberish; + + /** + * The chain ID for the network this transaction is valid on. + */ chainId?: null | BigNumberish; + /** + * The [[link-eip-2930]] access list. Storage slots included in the access + * list are //warmed// by pre-loading them, so their initial cost to + * fetch is guaranteed, but then each additional access is cheaper. + */ accessList?: null | AccessListish; + /** + * A custom object, which can be passed along for network-specific + * values. + */ customData?: any; // Only meaningful when used for call + + /** + * When using ``call`` or ``estimateGas``, this allows a specific + * block to be queried. Many backends do not support this and when + * unsupported errors are silently squelched and ``"latest"`` is used. + */ blockTag?: BlockTag; + + /** + * When using ``call``, this enables CCIP-read, which permits the + * provider to be redirected to web-based content during execution, + * which is then further validated by the contract. + * + * There are potential security implications allowing CCIP-read, as + * it could be used to expose the IP address or user activity during + * the fetch to unexpected parties. + */ enableCcipRead?: boolean; // Todo? //gasMultiplier?: number; }; +/** + * A **PreparedTransactionRequest** is identical to a [[TransactionRequest]] + * except all the property types are strictly enforced. + */ export interface PreparedTransactionRequest { + /** + * The transaction type. + */ type?: number; + + /** + * The target of the transaction. + */ to?: AddressLike; + + /** + * The sender of the transaction. + */ from?: AddressLike; + /** + * The nonce of the transaction, used to prevent replay attacks. + */ + nonce?: number; + /** + * The maximum amount of gas to allow this transaction to consime. + */ gasLimit?: bigint; + + /** + * The gas price to use for legacy transactions or transactions on + * legacy networks. + * + * Most of the time the ``max*FeePerGas`` is preferred. + */ gasPrice?: bigint; + /** + * The [[link-eip-1559]] maximum priority fee to pay per gas. + */ maxPriorityFeePerGas?: bigint; + + /** + * The [[link-eip-1559]] maximum total fee to pay per gas. The actual + * value used is protocol enforced to be the block's base fee. + */ maxFeePerGas?: bigint; + /** + * The transaction data. + */ data?: string; + + + /** + * The transaction value (in wei). + */ value?: bigint; + + /** + * The chain ID for the network this transaction is valid on. + */ chainId?: bigint; + /** + * The [[link-eip-2930]] access list. Storage slots included in the access + * list are //warmed// by pre-loading them, so their initial cost to + * fetch is guaranteed, but then each additional access is cheaper. + */ accessList?: AccessList; + /** + * A custom object, which can be passed along for network-specific + * values. + */ customData?: any; + + + /** + * When using ``call`` or ``estimateGas``, this allows a specific + * block to be queried. Many backends do not support this and when + * unsupported errors are silently squelched and ``"latest"`` is used. + */ blockTag?: BlockTag; + + /** + * When using ``call``, this enables CCIP-read, which permits the + * provider to be redirected to web-based content during execution, + * which is then further validated by the contract. + * + * There are potential security implications allowing CCIP-read, as + * it could be used to expose the IP address or user activity during + * the fetch to unexpected parties. + */ enableCcipRead?: boolean; } +/** + * Returns a copy of %%req%% with all properties coerced to their strict + * types. + */ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest { const result: any = { }; @@ -206,10 +372,30 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest * Before a block is included, it is a //pending// block. */ export interface MinedBlock extends Block { + /** + * The block number also known as the block height. + */ readonly number: number; + + /** + * The block hash. + */ readonly hash: string; + + /** + * The block timestamp, in seconds from epoch. + */ readonly timestamp: number; + + /** + * The block date, created from the [[timestamp]]. + */ readonly date: Date; + + /** + * The miner of the block, also known as the ``author`` or + * block ``producer``. + */ readonly miner: string; } @@ -218,6 +404,7 @@ export interface MinedBlock extends Block { * Ethereum. */ export class Block implements BlockParams, Iterable { + /** * The provider connected to the block used to fetch additional details * if necessary. @@ -232,6 +419,9 @@ export class Block implements BlockParams, Iterable { /** * The block hash. + * + * This hash includes all properties, so can be safely used to identify + * an exact set of block properties. */ readonly hash!: null | string; @@ -348,7 +538,7 @@ export class Block implements BlockParams, Iterable { /** * Returns the complete transactions for blocks which * prefetched them, by passing ``true`` to %%prefetchTxs%% - * into [[provider_getBlock]]. + * into [[Provider-getBlock]]. */ get prefetchedTransactions(): Array { const txs = this.#transactions.slice(); @@ -445,6 +635,12 @@ export class Block implements BlockParams, Iterable { } } + /** + * If a **Block** was fetched with a request to include the transactions + * this will allow synchronous access to those transactions. + * + * If the transactions were not prefetched, this will throw. + */ getPrefetchedTransaction(indexOrHash: number | string): TransactionResponse { const txs = this.prefetchedTransactions; if (typeof(indexOrHash) === "number") { @@ -460,20 +656,21 @@ export class Block implements BlockParams, Iterable { } /** - * Has this block been mined. - * - * If true, the block has been typed-gaurded that all mined - * properties are non-null. + * Returns true if this block been mined. This provides a type guard + * for all properties on a [[MinedBlock]]. */ isMined(): this is MinedBlock { return !!this.hash; } /** - * + * Returns true if this block is an [[link-eip-2930]] block. */ isLondon(): this is (Block & { baseFeePerGas: bigint }) { return !!this.baseFeePerGas; } + /** + * @_ignore: + */ orphanedEvent(): OrphanFilter { if (!this.isMined()) { throw new Error(""); } return createOrphanedBlockFilter(this); @@ -483,24 +680,80 @@ export class Block implements BlockParams, Iterable { ////////////////////// // Log +/** + * A **Log** in Ethereum represents an event that has been included in a + * transaction using the ``LOG*`` opcodes, which are most commonly used by + * Solidity's emit for announcing events. + */ export class Log implements LogParams { + + /** + * The provider connected to the log used to fetch additional details + * if necessary. + */ readonly provider: Provider; + /** + * The transaction hash of the transaction this log occurred in. Use the + * [[Log-getTransaction]] to get the [[TransactionResponse]]. + */ readonly transactionHash!: string; + + /** + * The block hash of the block this log occurred in. Use the + * [[Log-getBlock]] to get the [[Block]]. + */ readonly blockHash!: string; + + /** + * The block number of the block this log occurred in. It is preferred + * to use the [[Block-hash]] when fetching the related [[Block]], + * since in the case of an orphaned block, the block at that height may + * have changed. + */ readonly blockNumber!: number; + /** + * If the **Log** represents a block that was removed due to an orphaned + * block, this will be true. + * + * This can only happen within an orphan event listener. + */ readonly removed!: boolean; + /** + * The address of the contract that emitted this log. + */ readonly address!: string; + + /** + * The data included in this log when it was emitted. + */ readonly data!: string; + /** + * The indexed topics included in this log when it was emitted. + * + * All topics are included in the bloom filters, so they can be + * efficiently filtered using the [[Provider-getLogs]] method. + */ readonly topics!: ReadonlyArray; + /** + * The index within the block this log occurred at. This is generally + * not useful to developers, but can be used with the various roots + * to proof inclusion within a block. + */ readonly index!: number; + + /** + * The index within the transaction of this log. + */ readonly transactionIndex!: number; - + /** + * @_ignore: + */ constructor(log: LogParams, provider: Provider) { this.provider = provider; @@ -522,6 +775,9 @@ export class Log implements LogParams { }); } + /** + * Returns a JSON-compatible object. + */ toJSON(): any { const { address, blockHash, blockNumber, data, index, @@ -535,24 +791,37 @@ export class Log implements LogParams { }; } + /** + * Returns the block that this log occurred in. + */ async getBlock(): Promise { const block = await this.provider.getBlock(this.blockHash); assert(!!block, "failed to find transaction", "UNKNOWN_ERROR", { }); return block; } + /** + * Returns the transaction that this log occurred in. + */ async getTransaction(): Promise { const tx = await this.provider.getTransaction(this.transactionHash); assert(!!tx, "failed to find transaction", "UNKNOWN_ERROR", { }); return tx; } + /** + * Returns the transaction receipt fot the transaction that this + * log occurred in. + */ async getTransactionReceipt(): Promise { const receipt = await this.provider.getTransactionReceipt(this.transactionHash); assert(!!receipt, "failed to find transaction receipt", "UNKNOWN_ERROR", { }); return receipt; } + /** + * @_ignore: + */ removedEvent(): OrphanFilter { return createRemovedLogFilter(this); } @@ -575,32 +844,118 @@ export interface ByzantiumTransactionReceipt { } */ +/** + * A **TransactionReceipt** includes additional information about a + * transaction that is only available after it has been mined. + */ export class TransactionReceipt implements TransactionReceiptParams, Iterable { + /** + * The provider connected to the log used to fetch additional details + * if necessary. + */ readonly provider!: Provider; + /** + * The address the transaction was send to. + */ readonly to!: null | string; + + /** + * The sender of the transaction. + */ readonly from!: string; + + /** + * The address of the contract if the transaction was directly + * responsible for deploying one. + * + * This is non-null **only** if the ``to`` is empty and the ``data`` + * was successfully executed as initcode. + */ readonly contractAddress!: null | string; + /** + * The transaction hash. + */ readonly hash!: string; + + /** + * The index of this transaction within the block transactions. + */ readonly index!: number; + /** + * The block hash of the [[Block]] this transaction was included in. + */ readonly blockHash!: string; + + /** + * The block number of the [[Block]] this transaction was included in. + */ readonly blockNumber!: number; + /** + * The bloom filter bytes that represent all logs that occurred within + * this transaction. This is generally not useful for most developers, + * but can be used to validate the included logs. + */ readonly logsBloom!: string; + /** + * The actual amount of gas used by this transaction. + * + * When creating a transaction, the amount of gas that will be used can + * only be approximated, but the sender must pay the gas fee for the + * entire gas limit. After the transaction, the difference is refunded. + */ readonly gasUsed!: bigint; + + /** + * The amount of gas used by all transactions within the block for this + * and all transactions with a lower ``index``. + * + * This is generally not useful for developers but can be used to + * validate certain aspects of execution. + */ readonly cumulativeGasUsed!: bigint; + + /** + * The actual gas price used during execution. + * + * Due to the complexity of [[link-eip-1559]] this value can only + * be caluclated after the transaction has been mined, snce the base + * fee is protocol-enforced. + */ readonly gasPrice!: bigint; + /** + * The [[link-eip-2718]] transaction type. + */ readonly type!: number; //readonly byzantium!: boolean; + + /** + * The status of this transaction, indicating success (i.e. ``1``) or + * a revert (i.e. ``0``). + * + * This is available in post-byzantium blocks, but some backends may + * backfill this value. + */ readonly status!: null | number; + + /** + * The root hash of this transaction. + * + * This is no present and was only included in pre-byzantium blocks, but + * could be used to validate certain parts of the receipt. + */ readonly root!: null | string; readonly #logs: ReadonlyArray; + /** + * @_ignore: + */ constructor(tx: TransactionReceiptParams, provider: Provider) { this.#logs = Object.freeze(tx.logs.map((log) => { return new Log(log, provider); @@ -639,8 +994,14 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable { return this.#logs; } + /** + * Returns a JSON-compatible representation. + */ toJSON(): any { const { to, from, contractAddress, hash, index, blockHash, blockNumber, logsBloom, @@ -661,6 +1022,9 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable { @@ -675,34 +1039,58 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable { const block = await this.provider.getBlock(this.blockHash); if (block == null) { throw new Error("TODO"); } return block; } + /** + * Resolves to the transaction this transaction occurred in. + */ async getTransaction(): Promise { const tx = await this.provider.getTransaction(this.hash); if (tx == null) { throw new Error("TODO"); } return tx; } + /** + * Resolves to the return value of the execution of this transaction. + * + * Support for this feature is limited, as it requires an archive node + * with the ``debug_`` or ``trace_`` API enabled. + */ async getResult(): Promise { return (await this.provider.getTransactionResult(this.hash)); } + /** + * Resolves to the number of confirmations this transaction has. + */ async confirmations(): Promise { return (await this.provider.getBlockNumber()) - this.blockNumber + 1; } + /** + * @_ignore: + */ removedEvent(): OrphanFilter { return createRemovedTransactionFilter(this); } + /** + * @_ignore: + */ reorderedEvent(other?: TransactionResponse): OrphanFilter { assert(!other || other.isMined(), "unmined 'other' transction cannot be orphaned", "UNSUPPORTED_OPERATION", { operation: "reorderedEvent(other)" }); @@ -714,22 +1102,38 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable, TransactionResponseParams { /** * The provider this is connected to, which will influence how its @@ -858,8 +1262,7 @@ export class TransactionResponse implements TransactionLike, Transaction #startBlock: number; /** - * Create a new TransactionResponse with %%tx%% parameters - * connected to %%provider%%. + * @_ignore: */ constructor(tx: TransactionResponseParams, provider: Provider) { this.provider = provider; @@ -893,7 +1296,7 @@ export class TransactionResponse implements TransactionLike, Transaction } /** - * Returns a JSON representation of this transaction. + * Returns a JSON-compatible representation of this transaction. */ toJSON(): any { const { @@ -1272,17 +1675,40 @@ export type TopicFilter = Array>; // @TODO: //export type DeferableTopicFilter = Array | Array>>; +/** + * An **EventFilter** allows efficiently filtering logs (also known as + * events) using bloom filters included within blocks. + */ export interface EventFilter { address?: AddressLike | Array; topics?: TopicFilter; } +/** + * A **Filter** allows searching a specific range of blocks for mathcing + * logs. + */ export interface Filter extends EventFilter { + + /** + * The start block for the filter (inclusive). + */ fromBlock?: BlockTag; + + /** + * The end block for the filter (inclusive). + */ toBlock?: BlockTag; } +/** + * A **FilterByBlockHash** allows searching a specific block for mathcing + * logs. + */ export interface FilterByBlockHash extends EventFilter { + /** + * The blockhash of the specific block for the filter. + */ blockHash?: string; } @@ -1290,6 +1716,30 @@ export interface FilterByBlockHash extends EventFilter { ////////////////////// // ProviderEvent +/** + * A **ProviderEvent** provides the types of events that can be subscribed + * to on a [[Provider]]. + * + * Each provider may include additional possible events it supports, but + * the most commonly supported are: + * + * **``"block"``** - calls the listener with the current block number on each + * new block. + * + * **``"error"``** - calls the listener on each async error that occurs during + * the event loop, with the error. + * + * **``"debug"``** - calls the listener on debug events, which can be used to + * troubleshoot network errors, provider problems, etc. + * + * **``transaction hash``** - calls the listener on each block after the + * transaction has been mined; generally ``.once`` is more appropriate for + * this event. + * + * **``Array``** - calls the listener on each log that matches the filter. + * + * [[EventFilter]] - calls the listener with each matching log + */ export type ProviderEvent = string | Array> | EventFilter | OrphanFilter; diff --git a/src.ts/providers/signer-noncemanager.ts b/src.ts/providers/signer-noncemanager.ts index f6d6ae051..724bfe89e 100644 --- a/src.ts/providers/signer-noncemanager.ts +++ b/src.ts/providers/signer-noncemanager.ts @@ -9,12 +9,23 @@ import type { import type { Signer } from "./signer.js"; +/** + * A **NonceManager** wraps another [[Signer]] and automatically manages + * the nonce, ensuring serialized and sequential nonces are used during + * transaction. + */ export class NonceManager extends AbstractSigner { + /** + * The Signer being managed. + */ signer!: Signer; #noncePromise: null | Promise; #delta: number; + /** + * Creates a new **NonceManager** to manage %%signer%%. + */ constructor(signer: Signer) { super(signer.provider); defineProperties(this, { signer }); @@ -44,10 +55,18 @@ export class NonceManager extends AbstractSigner { return super.getNonce(blockTag); } + /** + * Manually increment the nonce. This may be useful when managng + * offline transactions. + */ increment(): void { this.#delta++; } + /** + * Resets the nonce, causing the **NonceManager** to reload the current + * nonce from the blockchain on the next transaction. + */ reset(): void { this.#delta = 0; this.#noncePromise = null; diff --git a/src.ts/providers/subscriber-filterid.ts b/src.ts/providers/subscriber-filterid.ts index b6fb21ac2..466cc2ce7 100644 --- a/src.ts/providers/subscriber-filterid.ts +++ b/src.ts/providers/subscriber-filterid.ts @@ -33,6 +33,11 @@ export class FilterIdSubscriber implements Subscriber { #hault: boolean; + /** + * Creates a new **FilterIdSubscriber** which will used [[_subscribe]] + * and [[_emitResults]] to setup the subscription and provide the event + * to the %%provider%%. + */ constructor(provider: JsonRpcApiProvider) { this.#provider = provider; @@ -46,14 +51,23 @@ export class FilterIdSubscriber implements Subscriber { this.#hault = false; } + /** + * Sub-classes **must** override this to begin the subscription. + */ _subscribe(provider: JsonRpcApiProvider): Promise { throw new Error("subclasses must override this"); } + /** + * Sub-classes **must** override this handle the events. + */ _emitResults(provider: AbstractProvider, result: Array): Promise { throw new Error("subclasses must override this"); } + /** + * Sub-classes **must** override this handle recovery on errors. + */ _recover(provider: AbstractProvider): Subscriber { throw new Error("subclasses must override this"); } @@ -141,6 +155,10 @@ export class FilterIdSubscriber implements Subscriber { export class FilterIdEventSubscriber extends FilterIdSubscriber { #event: EventFilter; + /** + * Creates a new **FilterIdEventSubscriber** attached to %%provider%% + * listening for %%filter%%. + */ constructor(provider: JsonRpcApiProvider, filter: EventFilter) { super(provider); this.#event = copy(filter); diff --git a/src.ts/providers/subscriber-polling.ts b/src.ts/providers/subscriber-polling.ts index 837c58f1e..7dfff2b7a 100644 --- a/src.ts/providers/subscriber-polling.ts +++ b/src.ts/providers/subscriber-polling.ts @@ -8,7 +8,7 @@ function copy(obj: any): any { } /** - * @TODO + * Return the polling subscriber for common events. * * @_docloc: api/providers/abstract-provider */ @@ -24,7 +24,8 @@ export function getPollingSubscriber(provider: AbstractProvider, event: Provider // @TODO: refactor this /** - * @TODO + * A **PollingBlockSubscriber** polls at a regular interval for a change + * in the block number. * * @_docloc: api/providers/abstract-provider */ @@ -38,6 +39,9 @@ export class PollingBlockSubscriber implements Subscriber { // indicates we still need to fetch an initial block number #blockNumber: number; + /** + * Create a new **PollingBlockSubscriber** attached to %%provider%%. + */ constructor(provider: AbstractProvider) { this.#provider = provider; this.#poller = null; @@ -46,6 +50,9 @@ export class PollingBlockSubscriber implements Subscriber { this.#blockNumber = -2; } + /** + * The polling interval. + */ get pollingInterval(): number { return this.#interval; } set pollingInterval(value: number) { this.#interval = value; } @@ -107,7 +114,8 @@ export class PollingBlockSubscriber implements Subscriber { } /** - * @TODO + * An **OnBlockSubscriber** can be sub-classed, with a [[_poll]] + * implmentation which will be called on every new block. * * @_docloc: api/providers/abstract-provider */ @@ -116,6 +124,9 @@ export class OnBlockSubscriber implements Subscriber { #poll: (b: number) => void; #running: boolean; + /** + * Create a new **OnBlockSubscriber** attached to %%provider%%. + */ constructor(provider: AbstractProvider) { this.#provider = provider; this.#running = false; @@ -124,6 +135,9 @@ export class OnBlockSubscriber implements Subscriber { } } + /** + * Called on every new block. + */ async _poll(blockNumber: number, provider: AbstractProvider): Promise { throw new Error("sub-classes must override this"); } @@ -148,7 +162,7 @@ export class OnBlockSubscriber implements Subscriber { } /** - * @TODO + * @_ignore: * * @_docloc: api/providers/abstract-provider */ @@ -167,13 +181,18 @@ export class PollingOrphanSubscriber extends OnBlockSubscriber { } /** - * @TODO + * A **PollingTransactionSubscriber** will poll for a given transaction + * hash for its receipt. * * @_docloc: api/providers/abstract-provider */ export class PollingTransactionSubscriber extends OnBlockSubscriber { #hash: string; + /** + * Create a new **PollingTransactionSubscriber** attached to + * %%provider%%, listening for %%hash%%. + */ constructor(provider: AbstractProvider, hash: string) { super(provider); this.#hash = hash; @@ -186,7 +205,7 @@ export class PollingTransactionSubscriber extends OnBlockSubscriber { } /** - * @TODO + * A **PollingEventSubscriber** will poll for a given filter for its logs. * * @_docloc: api/providers/abstract-provider */ @@ -201,6 +220,10 @@ export class PollingEventSubscriber implements Subscriber { // indicates we still need to fetch an initial block number #blockNumber: number; + /** + * Create a new **PollingTransactionSubscriber** attached to + * %%provider%%, listening for %%filter%%. + */ constructor(provider: AbstractProvider, filter: EventFilter) { this.#provider = provider; this.#filter = copy(filter); diff --git a/src.ts/transaction/index.ts b/src.ts/transaction/index.ts index 1e18e43cf..a67c044de 100644 --- a/src.ts/transaction/index.ts +++ b/src.ts/transaction/index.ts @@ -1,5 +1,5 @@ /** - * Transactions.. + * Each state-changing operation on Ethereum requires a transaction. * * @_section api/transaction:Transactions [about-transactions] */ diff --git a/src.ts/transaction/transaction.ts b/src.ts/transaction/transaction.ts index 915f50daa..2131fd631 100644 --- a/src.ts/transaction/transaction.ts +++ b/src.ts/transaction/transaction.ts @@ -22,6 +22,11 @@ const BN_28 = BigInt(28) const BN_35 = BigInt(35); const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); +/** + * A **TransactionLike** is an object which is appropriate as a loose + * input for many operations which will populate missing properties of + * a transaction. + */ export interface TransactionLike { /** * The type. diff --git a/src.ts/utils/errors.ts b/src.ts/utils/errors.ts index 8ecc99b91..68c84702e 100644 --- a/src.ts/utils/errors.ts +++ b/src.ts/utils/errors.ts @@ -1,5 +1,9 @@ /** - * About Errors. + * All errors in ethers include properties to ensure they are both + * human-readable (i.e. ``.message``) and machine-readable (i.e. ``.code``). + * + * The [[isError]] function can be used to check the error ``code`` and + * provide a type guard for the properties present on that error interface. * * @_section: api/utils/errors:Errors [about-errors] */ @@ -14,6 +18,10 @@ import type { import type { FetchRequest, FetchResponse } from "./fetch.js"; +/** + * An error may contain additional properties, but those must not + * conflict with any impliciat properties. + */ export type ErrorInfo = Omit; @@ -202,6 +210,9 @@ export interface UnsupportedOperationError extends EthersError<"UNSUPPORTED_OPER operation: string; } +/** + * This Error indicates a proplem connecting to a network. + */ export interface NetworkError extends EthersError<"NETWORK_ERROR"> { event: string; } diff --git a/src.ts/utils/events.ts b/src.ts/utils/events.ts index ac8fb7aed..532cb404b 100644 --- a/src.ts/utils/events.ts +++ b/src.ts/utils/events.ts @@ -1,5 +1,7 @@ /** - * Explain events... + * Events allow for applications to use the observer pattern, which + * allows subscribing and publishing events, outside the normal + * execution paths. * * @_section api/utils/events:Events [about-events] */ diff --git a/src.ts/utils/fetch.ts b/src.ts/utils/fetch.ts index 74ecdefde..47bcc9e6f 100644 --- a/src.ts/utils/fetch.ts +++ b/src.ts/utils/fetch.ts @@ -1,5 +1,19 @@ /** - * Explain fetching here... + * Fetching content from the web is environment-specific, so Ethers + * provides an abstraction the each environment can implement to provide + * this service. + * + * On [Node.js](link-node), the ``http`` and ``https`` libs are used to + * create a request object, register event listeners and process data + * and populate the [[FetchResponse]]. + * + * In a browser, the [DOM fetch](link-js-fetch) is used, and the resulting + * ``Promise`` is waited on to retreive the payload. + * + * The [[FetchRequest]] is responsible for handling many common situations, + * such as redirects, server throttling, authentcation, etc. + * + * It also handles common gateways, such as IPFS and data URIs. * * @_section api/utils/fetching:Fetching Web Content [about-fetch] */ @@ -11,7 +25,9 @@ import { toUtf8Bytes, toUtf8String } from "./utf8.js" import { getUrl } from "./geturl.js"; - +/** + * An environments implementation of ``getUrl`` must return this type. + */ export type GetUrlResponse = { statusCode: number, statusMessage: string, @@ -19,10 +35,15 @@ export type GetUrlResponse = { body: null | Uint8Array }; +/** + * This can be used to control how throttling is handled in + * [[FetchRequest-setThrottleParams]]. + */ export type FetchThrottleParams = { maxAttempts?: number; slotInterval?: number; }; + /** * Called before any network request, allowing updated headers (e.g. Bearer tokens), etc. */ diff --git a/src.ts/utils/fixednumber.ts b/src.ts/utils/fixednumber.ts index 6d040ee2a..5f700e31d 100644 --- a/src.ts/utils/fixednumber.ts +++ b/src.ts/utils/fixednumber.ts @@ -1,5 +1,10 @@ /** - * About fixed-point math... + * The **FixedNumber** class permits using values with decimal places, + * using fixed-pont math. + * + * Fixed-point math is still based on integers under-the-hood, but uses an + * internal offset to store fractional components below, and each operation + * corrects for this after each operation. * * @_section: api/utils/fixed-point-math:Fixed-Point Maths [about-fixed-point-math] */ diff --git a/src.ts/utils/maths.ts b/src.ts/utils/maths.ts index ad53c3766..c25914715 100644 --- a/src.ts/utils/maths.ts +++ b/src.ts/utils/maths.ts @@ -113,6 +113,10 @@ export function getBigInt(value: BigNumberish, name?: string): bigint { assertArgument(false, "invalid BigNumberish value", name || "value", value); } +/** + * Returns %%value%% as a bigint, validating it is valid as a bigint + * value and that it is positive. + */ export function getUint(value: BigNumberish, name?: string): bigint { const result = getBigInt(value, name); assert(result >= BN_0, "unsigned value cannot be negative", "NUMERIC_FAULT", { diff --git a/src.ts/wordlists/wordlist-owl.ts b/src.ts/wordlists/wordlist-owl.ts index 43832e142..a7f2c9220 100644 --- a/src.ts/wordlists/wordlist-owl.ts +++ b/src.ts/wordlists/wordlist-owl.ts @@ -18,7 +18,7 @@ import { Wordlist } from "./wordlist.js"; * based on ASCII-7 small. * * If necessary, there are tools within the ``generation/`` folder - * to create these necessary data. + * to create the necessary data. */ export class WordlistOwl extends Wordlist { #data: string; @@ -35,8 +35,14 @@ export class WordlistOwl extends Wordlist { this.#words = null; } + /** + * The OWL-encoded data. + */ get _data(): string { return this.#data; } + /** + * Decode all the words for the wordlist. + */ _decodeWords(): Array { return decodeOwl(this.#data); } diff --git a/src.ts/wordlists/wordlist-owla.ts b/src.ts/wordlists/wordlist-owla.ts index 391629e16..d29c2bb6a 100644 --- a/src.ts/wordlists/wordlist-owla.ts +++ b/src.ts/wordlists/wordlist-owla.ts @@ -12,18 +12,29 @@ import { decodeOwlA } from "./decode-owla.js"; * based on latin-1 small. * * If necessary, there are tools within the ``generation/`` folder - * to create these necessary data. + * to create the necessary data. */ export class WordlistOwlA extends WordlistOwl { #accent: string; + + /** + * Creates a new Wordlist for %%locale%% using the OWLA %%data%% + * and %%accent%% data and validated against the %%checksum%%. + */ constructor(locale: string, data: string, accent: string, checksum: string) { super(locale, data, checksum); this.#accent = accent; } + /** + * The OWLA-encoded accent data. + */ get _accent(): string { return this.#accent; } + /** + * Decode all the words for the wordlist. + */ _decodeWords(): Array { return decodeOwlA(this._data, this._accent); }