docs: added jsdocs and general documentation

This commit is contained in:
Richard Moore 2023-06-01 17:42:48 -04:00
parent c785c1e515
commit bbcfb5f6b8
45 changed files with 2923 additions and 208 deletions

@ -1,26 +1,97 @@
_section: ABI
_section: Application Binary Interfaces @<docs-abi>
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.

@ -1,7 +1,8 @@
_section: Ethereum Basics @priority<99>
_section: Ethereum Basics @<docs-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)

@ -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)

@ -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)

@ -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)

@ -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 {

@ -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<string>): ReadonlySet<string> {
@ -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<any>): 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<ConstructorFragment>(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<FallbackFragment>(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<any>): 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);
}

@ -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

@ -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<LogDescription>(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<TransactionDescription>(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<ErrorDescription>(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<Indexed>(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<Fragment | JsonFragment | string>;

@ -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<any | Typed>, 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<any | Typed> | Record<string, any | Typed>, 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<string, any>): Typed {
return new Typed(_gaurd, "overrides", Object.assign({ }, v));
}

@ -608,16 +608,49 @@ async function emit(contract: BaseContract, event: ContractEventName, args: Arra
const passProperties = [ "then" ];
export class BaseContract implements Addressable, EventEmitterable<ContractEventName> {
/**
* 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<string, ContractEvent>;
/**
* @_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<ContractEvent
}
/**
* Return a new Contract instance with the same target and ABI, but
* a different %%runner%%.
*/
connect(runner: null | ContractRunner): BaseContract {
return new BaseContract(this.target, this.interface, runner);
}
/**
* Return the resolved address of this Contract.
*/
async getAddress(): Promise<string> { return await getInternal(this).addrPromise; }
/**
* Return the dedployed bytecode or null if no bytecode is found.
*/
async getDeployedCode(): Promise<null | string> {
const provider = getProvider(this.runner);
assert(provider, "runner does not support .provider",
@ -743,6 +786,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return code;
}
/**
* Resolve to this Contract once the bytecode has been deployed, or
* resolve immediately if already deployed.
*/
async waitForDeployment(): Promise<this> {
// 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<ContractEvent
});
}
/**
* Return the transaction used to deploy this contract.
*
* This is only available if this instance was returned from a
* [[ContractFactory]].
*/
deploymentTransaction(): null | ContractTransactionResponse {
return getInternal(this).deployTx;
}
/**
* Return the function for a given name. This is useful when a contract
* method name conflicts with a JavaScript name such as ``prototype`` or
* when using a Contract programatically.
*/
getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T {
if (typeof(key) !== "string") { key = key.format(); }
const func = buildWrappedMethod(this, key);
return <T>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<Array<EventLog>> {
// 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<Array<EventLog | Log>> {
if (fromBlock == null) { fromBlock = 0; }
if (toBlock == null) { toBlock = "latest"; }
@ -822,6 +893,9 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
});
}
/**
* Add an event %%listener%% for the %%event%%.
*/
async on(event: ContractEventName, listener: Listener): Promise<this> {
const sub = await getSub(this, "on", event);
sub.listeners.push({ listener, once: false });
@ -829,6 +903,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return this;
}
/**
* Add an event %%listener%% for the %%event%%, but remove the listener
* after it is fired once.
*/
async once(event: ContractEventName, listener: Listener): Promise<this> {
const sub = await getSub(this, "once", event);
sub.listeners.push({ listener, once: true });
@ -836,10 +914,19 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return this;
}
/**
* Emit an %%event%% calling all listeners with %%args%%.
*
* Resolves to ``true`` if any listeners were called.
*/
async emit(event: ContractEventName, ...args: Array<any>): Promise<boolean> {
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<number> {
if (event) {
const sub = await hasSub(this, event);
@ -856,6 +943,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return total;
}
/**
* Resolves to the listeners subscribed to %%event%% or all listeners
* if unspecified.
*/
async listeners(event?: ContractEventName): Promise<Array<Listener>> {
if (event) {
const sub = await hasSub(this, event);
@ -872,6 +963,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return result;
}
/**
* Remove the %%listener%% from the listeners for %%event%% or remove
* all listeners if unspecified.
*/
async off(event: ContractEventName, listener?: Listener): Promise<this> {
const sub = await hasSub(this, event);
if (!sub) { return this; }
@ -889,6 +984,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return this;
}
/**
* Remove all the listeners for %%event%% or remove all listeners if
* unspecified.
*/
async removeAllListeners(event?: ContractEventName): Promise<this> {
if (event) {
const sub = await hasSub(this, event);
@ -906,16 +1005,23 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return this;
}
// Alias for "on"
/**
* Alias for [on].
*/
async addListener(event: ContractEventName, listener: Listener): Promise<this> {
return await this.on(event, listener);
}
// Alias for "off"
/**
* Alias for [off].
*/
async removeListener(event: ContractEventName, listener: Listener): Promise<this> {
return await this.off(event, listener);
}
/**
* Create a new Class for the %%abi%%.
*/
static buildClass<T = ContractInterface>(abi: InterfaceAbi): new (target: string, runner?: null | ContractRunner) => BaseContract & Omit<T, keyof BaseContract> {
class CustomContract extends BaseContract {
constructor(address: string, runner: null | ContractRunner = null) {
@ -925,6 +1031,9 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
return CustomContract as any;
};
/**
* Create a new BaseContract with a specified Interface.
*/
static from<T = ContractInterface>(target: string, abi: InterfaceAbi, runner?: null | ContractRunner): BaseContract & Omit<T, keyof BaseContract> {
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() { }

@ -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<A extends Array<any> = Array<any>, 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<A extends Array<any> = Array<any>, I = BaseContract
});
}
/**
* Resolves to the transaction to deploy the contract, passing %%args%%
* into the constructor.
*/
async getDeployTransaction(...args: ContractMethodArgs<A>): Promise<ContractDeployTransaction> {
let overrides: Omit<ContractDeployTransaction, "data"> = { };
@ -61,6 +88,14 @@ export class ContractFactory<A extends Array<any> = Array<any>, 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<A>): Promise<BaseContract & { deploymentTransaction(): ContractTransactionResponse } & Omit<I, keyof BaseContract>> {
const tx = await this.getDeployTransaction(...args);
@ -73,10 +108,17 @@ export class ContractFactory<A extends Array<any> = Array<any>, I = BaseContract
return new (<any>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<A, I> {
return new ContractFactory(this.interface, this.bytecode, runner);
}
/**
* Create a new **ContractFactory** from the standard Solidity JSON output.
*/
static fromSolidity<A extends Array<any> = Array<any>, I = ContractInterface>(output: any, runner?: ContractRunner): ContractFactory<A, I> {
assertArgument(output != null, "bad compiler output", "output", output);

@ -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]
*/

@ -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<TopicFilter>;
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<ContractTransaction, "to"> { }
// Overrides; cannot override `to` or `data` as Contract populates these
/**
* The overrides for a contract transaction.
*/
export interface Overrides extends Omit<TransactionRequest, "to" | "data"> { };
// 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 extends Array<any>> = 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<A extends Array<any>> = PostfixOverrides<{ [ I in keyof A ]-?: A[I] | Typed }>;
// A = Arguments passed in as a tuple
@ -45,51 +98,139 @@ export type ContractMethodArgs<A extends Array<any>> = 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<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> {
(...args: ContractMethodArgs<A>): Promise<D>;
/**
* 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<A>): FunctionFragment;
/**
* Returns a populated transaction that can be used to perform the
* contract method with %%args%%.
*/
populateTransaction(...args: ContractMethodArgs<A>): Promise<ContractTransaction>;
/**
* 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<A>): Promise<R>;
/**
* Send a transaction for the contract method with %%args%%.
*/
send(...args: ContractMethodArgs<A>): Promise<ContractTransactionResponse>;
/**
* Estimate the gas to send the contract method with %%args%%.
*/
estimateGas(...args: ContractMethodArgs<A>): Promise<bigint>;
/**
* Call the contract method with %%args%% and return the Result
* without any dereferencing.
*/
staticCallResult(...args: ContractMethodArgs<A>): Promise<Result>;
}
/**
* A contract method on a Contract.
*/
export interface ContractMethod<
A extends Array<any> = Array<any>,
R = any,
D extends R | ContractTransactionResponse = R | ContractTransactionResponse
> extends BaseContractMethod<A, R, D> { }
/**
* A pure of view method on a Contract.
*/
export interface ConstantContractMethod<
A extends Array<any>,
R = any
> extends ContractMethod<A, R, R> { }
// 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<A extends Array<any>> = { [ I in keyof A ]?: A[I] | Typed | null };
export interface ContractEvent<A extends Array<any> = Array<any>> {
(...args: ContractEventArgs<A>): 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<A>): EventFragment;
};
/**
* A Fallback or Receive function on a Contract.
*/
export interface WrappedFallback {
(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransactionResponse>;
/**
* Returns a populated transaction that can be used to perform the
* fallback method.
*
* For non-receive fallback, ``data`` may be overridden.
*/
populateTransaction(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransaction>;
/**
* Call the contract fallback and return the result.
*
* For non-receive fallback, ``data`` may be overridden.
*/
staticCall(overrides?: Omit<TransactionRequest, "to">): Promise<string>;
/**
* Send a transaction to the contract fallback.
*
* For non-receive fallback, ``data`` may be overridden.
*/
send(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransactionResponse>;
/**
* Estimate the gas to send a transaction to the contract fallback.
*
* For non-receive fallback, ``data`` may be overridden.
*/
estimateGas(overrides?: Omit<TransactionRequest, "to">): Promise<bigint>;
}

@ -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<EventLog>(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<EventLog | Log> {
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<null | ContractTransactionReceipt> {
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<ContractEventName> {
/**
* 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<ContractUnknownEventPayload>(this, { log });
}
/**
* Resolves to the block the event occured in.
*/
async getBlock(): Promise<Block> {
return await this.log.getBlock();
}
/**
* Resolves to the transaction the event occured in.
*/
async getTransaction(): Promise<TransactionResponse> {
return await this.log.getTransaction();
}
/**
* Resolves to the transaction receipt the event occured in.
*/
async getTransactionReceipt(): Promise<TransactionReceipt> {
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<ContractEventPayload>(this, { args, fragment });
}
/**
* The event name.
*/
get eventName(): string {
return this.fragment.name;
}
/**
* The event signature.
*/
get eventSignature(): string {
return this.fragment.format();
}

@ -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();

@ -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]
*/

@ -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<TypedDataField>): 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<string, Array<TypedDataField>> {
return JSON.parse(this.#types);
}
@ -159,6 +213,13 @@ export class TypedDataEncoder {
readonly #encoderCache: Map<string, (value: any) => 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<string, Array<TypedDataField>>) {
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, any>): string {
return keccak256(this.encodeData(name, value));
}
/**
* Return the fulled encoded %%value%% for the [[types]].
*/
encode(value: Record<string, any>): string {
return this.encodeData(this.primaryType, value);
}
/**
* Return the hash of the fully encoded %%value%% for the [[types]].
*/
hash(value: Record<string, any>): 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<string, any>, callback: (type: string, data: any) => any): any {
return this._visit(this.primaryType, value, callback);
}
/**
* Create a new **TypedDataEncoder** for %%types%%.
*/
static from(types: Record<string, Array<TypedDataField>>): TypedDataEncoder {
return new TypedDataEncoder(types);
}
/**
* Return the primary type for %%types%%.
*/
static getPrimaryType(types: Record<string, Array<TypedDataField>>): string {
return TypedDataEncoder.from(types).primaryType;
}
/**
* Return the hashed struct for %%value%% using %%types%% and %%name%%.
*/
static hashStruct(name: string, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return TypedDataEncoder.from(types).hashStruct(name, value);
}
/**
* Return the domain hash for %%domain%%.
*/
static hashDomain(domain: TypedDataDomain): string {
const domainFields: Array<TypedDataField> = [ ];
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<string, Array<TypedDataField>>, value: Record<string, any>): 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<string, Array<TypedDataField>>, value: Record<string, any>): 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<string, Array<TypedDataField>>, value: Record<string, any>, resolveName: (name: string) => Promise<string>): 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<string, Array<TypedDataField>>, value: Record<string, any>): any {
// Validate the domain fields
TypedDataEncoder.hashDomain(domain);

@ -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<UnmanagedSubscriber>(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<string>;
topics?: Array<null | string | Array<string>>;
@ -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<any>
};
/**
* 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<string, Sub>;
@ -352,8 +434,11 @@ export class AbstractProvider implements Provider {
#disableCcipRead: boolean;
// @TODO: This should be a () => Promise<Network> 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<AbstractProviderPlugin> {
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<T extends AbstractProviderPlugin = AbstractProviderPlugin>(name: string): null | T {
return <T>(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<string>): Promise<null | string> {
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<Network> {
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<T = any>(req: PerformActionRequest): Promise<T> {
assert(false, `unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", {
operation: req.method,
@ -511,16 +646,26 @@ export class AbstractProvider implements Provider {
}
// State
async getBlockNumber(): Promise<number> {
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<string> {
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<string> {
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<PerformActionFilter> {
// Create a canonical representation of the topics
@ -619,6 +769,11 @@ export class AbstractProvider implements Provider {
return resolve(<Array<string>>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<PerformActionTransaction> {
const request = <PerformActionTransaction>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; }

@ -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<P extends null | Provider = null | Provider> implements Signer {
/**
* The provider this signer is connected to.
*/
readonly provider!: P;
/**
* Creates a new Signer connected to %%provider%%.
*/
constructor(provider?: P) {
defineProperties<AbstractSigner>(this, { provider: (provider || null) });
}
/**
* Resolves to the Signer address.
*/
abstract getAddress(): Promise<string>;
/**
* 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<number> {
@ -216,9 +240,24 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
abstract signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
}
/**
* 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<VoidSigner>(this, { address });

@ -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<bigint>;
// 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<string>;
// Required to support ENS names; usually a Signer or Provider
/**
* Required to support ENS names
*/
resolveName?: (name: string) => Promise<null | string>;
// Required for mutating calls; usually a Signer
/**
* Required for state mutating calls
*/
sendTransaction?: (tx: TransactionRequest) => Promise<TransactionResponse>;
}

@ -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<AvatarLinkage>;
/**
* 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<MulticoinProviderPlugin>(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<string> {
throw new Error("unsupported coin");
}
/**
* Resovles to the decoded %%data%% for %%coinType%%.
*/
async decodeAddress(coinType: number, data: BytesLike): Promise<string> {
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);
}

@ -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<string | TransactionResponseParams>;
};
@ -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<string>;
/**
* 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<LogParams>;
/**
* 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;
};

@ -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";

@ -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<string | bigint, () => 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<string, NetworkPlugin>;
/**
* 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) };
}

@ -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<NetworkPlugin>(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<EnsPlugin>(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<FeeData>;
/**
* The fee data function provided to the constructor.
*/
get feeDataFunc(): (provider: Provider) => Promise<FeeData> {
return this.#feeDataFunc;
}
/**
* Creates a new **FeeDataNetworkPlugin**.
*/
constructor(feeDataFunc: (provider: Provider) => Promise<FeeData>) {
super("org.ethers.plugins.network.FeeData");
this.#feeDataFunc = feeDataFunc;
}
/**
* Resolves to the fee data.
*/
async getFeeData(provider: Provider): Promise<FeeData> {
return await this.#feeDataFunc(provider);
}

@ -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<any> | Record<string, any> }): Promise<any>;
};
/**
* The possible additional events dispatched when using the ``"debug"``
* event on a [[BrowserProvider]].
*/
export type DebugEventBrowserProvider = {
action: "sendEip1193Payload",
payload: { method: string, params: Array<any> }
@ -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<any> | Record<string, any>) => Promise<any>;
/**
* 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<boolean> {
if (address == null) { address = 0; }

@ -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<FallbackProviderConfig> {
// 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<TallyResult>): 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<Config>;
@ -331,6 +373,13 @@ export class FallbackProvider extends AbstractProvider {
#height: number;
#initialSyncPromise: null | Promise<void>;
/**
* 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<AbstractProvider | FallbackProviderConfig>, 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<any> {
switch (req.method) {
case "broadcastTransaction":

@ -22,8 +22,17 @@ function splitBuffer(data: Buffer): { messages: Array<string>, 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) {

@ -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<any> | Record<string, any>;
/**
* 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<string> }>;
}

@ -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<any> { return JSON.parse(this.#filter); }
#filterId: null | Promise<string |number>;
@ -38,6 +46,10 @@ export class SocketSubscriber implements Subscriber {
#emitPromise: null | Promise<void>;
/**
* Creates a new **SocketSubscriber** attached to %%provider%% listening
* to %%filter%%.
*/
constructor(provider: SocketProvider, filter: Array<any>) {
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<void> {
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<number, { payload: JsonRpcPayload, resolve: (r: any) => void, reject: (e: Error) => void }>;
@ -144,6 +189,11 @@ export class SocketProvider extends JsonRpcApiProvider {
// registering, queue them
#pending: Map<number | string, Array<any>>;
/**
* 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<void> {
const result = <JsonRpcResult | JsonRpcError | JsonRpcSubscription>(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<void> {
throw new Error("sub-classes must override this");
}

@ -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>) => any);
onmessage: null | ((...args: Array<any>) => 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;

@ -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<string> {
/**
* The provider connected to the block used to fetch additional details
* if necessary.
@ -232,6 +419,9 @@ export class Block implements BlockParams, Iterable<string> {
/**
* 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<string> {
/**
* Returns the complete transactions for blocks which
* prefetched them, by passing ``true`` to %%prefetchTxs%%
* into [[provider_getBlock]].
* into [[Provider-getBlock]].
*/
get prefetchedTransactions(): Array<TransactionResponse> {
const txs = this.#transactions.slice();
@ -445,6 +635,12 @@ export class Block implements BlockParams, Iterable<string> {
}
}
/**
* 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<string> {
}
/**
* 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<string> {
//////////////////////
// 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<string>;
/**
* 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<Block> {
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<TransactionResponse> {
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<TransactionReceipt> {
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<Log> {
/**
* 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<Log>;
/**
* @_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<Lo
});
}
/**
* The logs for this transaction.
*/
get logs(): ReadonlyArray<Log> { 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<Lo
};
}
/**
* @_ignore:
*/
get length(): number { return this.logs.length; }
[Symbol.iterator](): Iterator<Log> {
@ -675,34 +1039,58 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable<Lo
};
}
/**
* The total fee for this transaction, in wei.
*/
get fee(): bigint {
return this.gasUsed * this.gasPrice;
}
/**
* Resolves to the block this transaction occurred in.
*/
async getBlock(): Promise<Block> {
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<TransactionResponse> {
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<string> {
return <string>(await this.provider.getTransactionResult(this.hash));
}
/**
* Resolves to the number of confirmations this transaction has.
*/
async confirmations(): Promise<number> {
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<Lo
//////////////////////
// Transaction Response
/**
* A **MinedTransactionResponse** is an interface representing a
* transaction which has been mined and allows for a type guard for its
* property values being defined.
*/
export interface MinedTransactionResponse extends TransactionResponse {
/**
* The block number this transaction occurred in.
*/
blockNumber: number;
/**
* The block hash this transaction occurred in.
*/
blockHash: string;
/**
* The date this transaction occurred on.
*/
date: Date;
}
/*
export type ReplacementDetectionSetup = {
to: string;
from: string;
value: bigint;
data: string;
nonce: number;
block: number;
};
*/
/**
* A **TransactionResponse** includes all properties about a transaction
* that was sent to the network, which may or may not be included in a
* block.
*
* The [[TransactionResponse-isMined]] can be used to check if the
* transaction has been mined as well as type guard that the otherwise
* possibly ``null`` properties are defined.
*/
export class TransactionResponse implements TransactionLike<string>, TransactionResponseParams {
/**
* The provider this is connected to, which will influence how its
@ -858,8 +1262,7 @@ export class TransactionResponse implements TransactionLike<string>, 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<string>, 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<null | string | Array<string>>;
// @TODO:
//export type DeferableTopicFilter = Array<null | string | Promise<string> | Array<string | Promise<string>>>;
/**
* An **EventFilter** allows efficiently filtering logs (also known as
* events) using bloom filters included within blocks.
*/
export interface EventFilter {
address?: AddressLike | Array<AddressLike>;
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<string | Array<string>> | EventFilter | OrphanFilter;

@ -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<number>;
#delta: number;
/**
* Creates a new **NonceManager** to manage %%signer%%.
*/
constructor(signer: Signer) {
super(signer.provider);
defineProperties<NonceManager>(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;

@ -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<string> {
throw new Error("subclasses must override this");
}
/**
* Sub-classes **must** override this handle the events.
*/
_emitResults(provider: AbstractProvider, result: Array<any>): Promise<void> {
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);

@ -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<void> {
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);

@ -1,5 +1,5 @@
/**
* Transactions..
* Each state-changing operation on Ethereum requires a transaction.
*
* @_section api/transaction:Transactions [about-transactions]
*/

@ -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<A = string> {
/**
* The type.

@ -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<T> = Omit<T, "code" | "name" | "message">;
@ -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;
}

@ -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]
*/

@ -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.
*/

@ -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]
*/

@ -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", {

@ -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<string> {
return decodeOwl(this.#data);
}

@ -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<string> {
return decodeOwlA(this._data, this._accent);
}