refactor: move brother_node development artifact to dev/test-nodes subdirectory

Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
2026-03-30 17:09:06 +02:00
parent bf730dcb4a
commit 816e258d4c
11734 changed files with 2001707 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,314 @@
/**
* Generally the [[Wallet]] and [[JsonRpcSigner]] and their sub-classes
* are sufficient for most developers, but this is provided to
* fascilitate more complex Signers.
*
* @_section: api/providers/abstract-signer: Subclassing Signer [abstract-signer]
*/
import { resolveAddress } from "../address/index.js";
import { Transaction } from "../transaction/index.js";
import {
defineProperties, getBigInt, resolveProperties,
assert, assertArgument
} from "../utils/index.js";
import { copyRequest } from "./provider.js";
import type {
AuthorizationRequest, TypedDataDomain, TypedDataField
} from "../hash/index.js";
import type { Authorization, TransactionLike } from "../transaction/index.js";
import type {
BlockTag, Provider, TransactionRequest, TransactionResponse
} from "./provider.js";
import type { Signer } from "./signer.js";
function checkProvider(signer: AbstractSigner, operation: string): Provider {
if (signer.provider) { return signer.provider; }
assert(false, "missing provider", "UNSUPPORTED_OPERATION", { operation });
}
async function populate(signer: AbstractSigner, tx: TransactionRequest): Promise<TransactionLike<string>> {
let pop: any = copyRequest(tx);
if (pop.to != null) { pop.to = resolveAddress(pop.to, signer); }
if (pop.from != null) {
const from = pop.from;
pop.from = Promise.all([
signer.getAddress(),
resolveAddress(from, signer)
]).then(([ address, from ]) => {
assertArgument(address.toLowerCase() === from.toLowerCase(),
"transaction from mismatch", "tx.from", from);
return address;
});
} else {
pop.from = signer.getAddress();
}
return await resolveProperties(pop);
}
/**
* 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> {
return checkProvider(this, "getTransactionCount").getTransactionCount(await this.getAddress(), blockTag);
}
async populateCall(tx: TransactionRequest): Promise<TransactionLike<string>> {
const pop = await populate(this, tx);
return pop;
}
async populateTransaction(tx: TransactionRequest): Promise<TransactionLike<string>> {
const provider = checkProvider(this, "populateTransaction");
const pop = await populate(this, tx);
if (pop.nonce == null) {
pop.nonce = await this.getNonce("pending");
}
if (pop.gasLimit == null) {
pop.gasLimit = await this.estimateGas(pop);
}
// Populate the chain ID
const network = await (<Provider>(this.provider)).getNetwork();
if (pop.chainId != null) {
const chainId = getBigInt(pop.chainId);
assertArgument(chainId === network.chainId, "transaction chainId mismatch", "tx.chainId", tx.chainId);
} else {
pop.chainId = network.chainId;
}
// Do not allow mixing pre-eip-1559 and eip-1559 properties
const hasEip1559 = (pop.maxFeePerGas != null || pop.maxPriorityFeePerGas != null);
if (pop.gasPrice != null && (pop.type === 2 || hasEip1559)) {
assertArgument(false, "eip-1559 transaction do not support gasPrice", "tx", tx);
} else if ((pop.type === 0 || pop.type === 1) && hasEip1559) {
assertArgument(false, "pre-eip-1559 transaction do not support maxFeePerGas/maxPriorityFeePerGas", "tx", tx);
}
if ((pop.type === 2 || pop.type == null) && (pop.maxFeePerGas != null && pop.maxPriorityFeePerGas != null)) {
// Fully-formed EIP-1559 transaction (skip getFeeData)
pop.type = 2;
} else if (pop.type === 0 || pop.type === 1) {
// Explicit Legacy or EIP-2930 transaction
// We need to get fee data to determine things
const feeData = await provider.getFeeData();
assert(feeData.gasPrice != null, "network does not support gasPrice", "UNSUPPORTED_OPERATION", {
operation: "getGasPrice" });
// Populate missing gasPrice
if (pop.gasPrice == null) { pop.gasPrice = feeData.gasPrice; }
} else {
// We need to get fee data to determine things
const feeData = await provider.getFeeData();
if (pop.type == null) {
// We need to auto-detect the intended type of this transaction...
if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) {
// The network supports EIP-1559!
// Upgrade transaction from null to eip-1559
if (pop.authorizationList && pop.authorizationList.length) {
pop.type = 4;
} else {
pop.type = 2;
}
if (pop.gasPrice != null) {
// Using legacy gasPrice property on an eip-1559 network,
// so use gasPrice as both fee properties
const gasPrice = pop.gasPrice;
delete pop.gasPrice;
pop.maxFeePerGas = gasPrice;
pop.maxPriorityFeePerGas = gasPrice;
} else {
// Populate missing fee data
if (pop.maxFeePerGas == null) {
pop.maxFeePerGas = feeData.maxFeePerGas;
}
if (pop.maxPriorityFeePerGas == null) {
pop.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
}
}
} else if (feeData.gasPrice != null) {
// Network doesn't support EIP-1559...
// ...but they are trying to use EIP-1559 properties
assert(!hasEip1559, "network does not support EIP-1559", "UNSUPPORTED_OPERATION", {
operation: "populateTransaction" });
// Populate missing fee data
if (pop.gasPrice == null) {
pop.gasPrice = feeData.gasPrice;
}
// Explicitly set untyped transaction to legacy
// @TODO: Maybe this shold allow type 1?
pop.type = 0;
} else {
// getFeeData has failed us.
assert(false, "failed to get consistent fee data", "UNSUPPORTED_OPERATION", {
operation: "signer.getFeeData" });
}
} else if (pop.type === 2 || pop.type === 3 || pop.type === 4) {
// Explicitly using EIP-1559 or EIP-4844
// Populate missing fee data
if (pop.maxFeePerGas == null) {
pop.maxFeePerGas = feeData.maxFeePerGas;
}
if (pop.maxPriorityFeePerGas == null) {
pop.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
}
}
}
//@TOOD: Don't await all over the place; save them up for
// the end for better batching
return await resolveProperties(pop);
}
async populateAuthorization(_auth: AuthorizationRequest): Promise<AuthorizationRequest> {
const auth = Object.assign({ }, _auth);
// Add a chain ID if not explicitly set to 0
if (auth.chainId == null) {
auth.chainId = (await checkProvider(this, "getNetwork").getNetwork()).chainId;
}
// @TODO: Take chain ID into account when populating noce?
if (auth.nonce == null) { auth.nonce = await this.getNonce(); }
return auth;
}
async estimateGas(tx: TransactionRequest): Promise<bigint> {
return checkProvider(this, "estimateGas").estimateGas(await this.populateCall(tx));
}
async call(tx: TransactionRequest): Promise<string> {
return checkProvider(this, "call").call(await this.populateCall(tx));
}
async resolveName(name: string): Promise<null | string> {
const provider = checkProvider(this, "resolveName");
return await provider.resolveName(name);
}
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
const provider = checkProvider(this, "sendTransaction");
const pop = await this.populateTransaction(tx);
delete pop.from;
const txObj = Transaction.from(pop);
return await provider.broadcastTransaction(await this.signTransaction(txObj));
}
// @TODO: in v7 move this to be abstract
authorize(authorization: AuthorizationRequest): Promise<Authorization> {
assert(false, "authorization not implemented for this signer",
"UNSUPPORTED_OPERATION", { operation: "authorize" });
}
abstract signTransaction(tx: TransactionRequest): Promise<string>;
abstract signMessage(message: string | Uint8Array): Promise<string>;
abstract signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
}
/**
* A **VoidSigner** is a class designed 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 });
}
async getAddress(): Promise<string> { return this.address; }
connect(provider: null | Provider): VoidSigner {
return new VoidSigner(this.address, provider);
}
#throwUnsupported(suffix: string, operation: string): never {
assert(false, `VoidSigner cannot sign ${ suffix }`, "UNSUPPORTED_OPERATION", { operation });
}
async signTransaction(tx: TransactionRequest): Promise<string> {
this.#throwUnsupported("transactions", "signTransaction");
}
async signMessage(message: string | Uint8Array): Promise<string> {
this.#throwUnsupported("messages", "signMessage");
}
async signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
this.#throwUnsupported("typed-data", "signTypedData");
}
}

49
dev/env/node_modules/ethers/src.ts/providers/community.ts generated vendored Executable file
View File

@@ -0,0 +1,49 @@
/**
* There are many awesome community services that provide Ethereum
* nodes both for developers just starting out and for large-scale
* communities.
*
* @_section: api/providers/thirdparty: Community Providers [thirdparty]
*/
/**
* Providers which offer community credentials should extend this
* to notify any interested consumers whether community credentials
* are in-use.
*/
export interface CommunityResourcable {
/**
* Returns true if the instance is connected using the community
* credentials.
*/
isCommunityResource(): boolean;
}
// Show the throttle message only once per service
const shown: Set<string> = new Set();
/**
* Displays a warning in the console when the community resource is
* being used too heavily by the app, recommending the developer
* acquire their own credentials instead of using the community
* credentials.
*
* The notification will only occur once per service.
*/
export function showThrottleMessage(service: string): void {
if (shown.has(service)) { return; }
shown.add(service);
console.log("========= NOTICE =========")
console.log(`Request-Rate Exceeded for ${ service } (this message will not be repeated)`);
console.log("");
console.log("The default API keys for each service are provided as a highly-throttled,");
console.log("community resource for low-traffic projects and early prototyping.");
console.log("");
console.log("While your application will continue to function, we highly recommended");
console.log("signing up for your own API keys to improve performance, increase your");
console.log("request rate/limit and enable other perks, such as metrics and advanced APIs.");
console.log("");
console.log("For more details: https:/\/docs.ethers.org/api-keys/");
console.log("==========================");
}

42
dev/env/node_modules/ethers/src.ts/providers/contracts.ts generated vendored Executable file
View File

@@ -0,0 +1,42 @@
import type {
Provider, TransactionRequest, TransactionResponse
} from "./provider.js";
/**
* 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.
*/
estimateGas?: (tx: TransactionRequest) => Promise<bigint>;
/**
* Required for pure, view or static calls to contracts.
*/
call?: (tx: TransactionRequest) => Promise<string>;
/**
* Required to support ENS names
*/
resolveName?: (name: string) => Promise<null | string>;
/**
* Required for state mutating calls
*/
sendTransaction?: (tx: TransactionRequest) => Promise<TransactionResponse>;
}

View File

@@ -0,0 +1,202 @@
import { assert } from "../utils/index.js";
import { AnkrProvider } from "./provider-ankr.js";
import { AlchemyProvider } from "./provider-alchemy.js";
//import { BlockscoutProvider } from "./provider-blockscout.js";
import { ChainstackProvider } from "./provider-chainstack.js";
import { CloudflareProvider } from "./provider-cloudflare.js";
import { EtherscanProvider } from "./provider-etherscan.js";
import { InfuraProvider } from "./provider-infura.js";
//import { PocketProvider } from "./provider-pocket.js";
import { QuickNodeProvider } from "./provider-quicknode.js";
import { FallbackProvider } from "./provider-fallback.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import { Network } from "./network.js";
import { WebSocketProvider } from "./provider-websocket.js";
import type { AbstractProvider } from "./abstract-provider.js";
import type { Networkish } from "./network.js";
import { WebSocketLike } from "./provider-websocket.js";
function isWebSocketLike(value: any): value is WebSocketLike {
return (value && typeof(value.send) === "function" &&
typeof(value.close) === "function");
}
const Testnets = "goerli kovan sepolia classicKotti optimism-goerli arbitrum-goerli matic-mumbai bnbt".split(" ");
/**
* Returns a default provider for %%network%%.
*
* If %%network%% is a [[WebSocketLike]] or string that begins with
* ``"ws:"`` or ``"wss:"``, a [[WebSocketProvider]] is returned backed
* by that WebSocket or URL.
*
* If %%network%% is a string that begins with ``"HTTP:"`` or ``"HTTPS:"``,
* a [[JsonRpcProvider]] is returned connected to that URL.
*
* Otherwise, a default provider is created backed by well-known public
* Web3 backends (such as [[link-infura]]) using community-provided API
* keys.
*
* The %%options%% allows specifying custom API keys per backend (setting
* an API key to ``"-"`` will omit that provider) and ``options.exclusive``
* can be set to either a backend name or and array of backend names, which
* will whitelist **only** those backends.
*
* Current backend strings supported are:
* - ``"alchemy"``
* - ``"ankr"``
* - ``"cloudflare"``
* - ``"chainstack"``
* - ``"etherscan"``
* - ``"infura"``
* - ``"publicPolygon"``
* - ``"quicknode"``
*
* @example:
* // Connect to a local Geth node
* provider = getDefaultProvider("http://localhost:8545/");
*
* // Connect to Ethereum mainnet with any current and future
* // third-party services available
* provider = getDefaultProvider("mainnet");
*
* // Connect to Polygon, but only allow Etherscan and
* // INFURA and use "MY_API_KEY" in calls to Etherscan.
* provider = getDefaultProvider("matic", {
* etherscan: "MY_API_KEY",
* exclusive: [ "etherscan", "infura" ]
* });
*/
export function getDefaultProvider(network?: string | Networkish | WebSocketLike, options?: any): AbstractProvider {
if (options == null) { options = { }; }
const allowService = (name: string) => {
if (options[name] === "-") { return false; }
if (typeof(options.exclusive) === "string") {
return (name === options.exclusive);
}
if (Array.isArray(options.exclusive)) {
return (options.exclusive.indexOf(name) !== -1);
}
return true;
};
if (typeof(network) === "string" && network.match(/^https?:/)) {
return new JsonRpcProvider(network);
}
if (typeof(network) === "string" && network.match(/^wss?:/) || isWebSocketLike(network)) {
return new WebSocketProvider(network);
}
// Get the network and name, if possible
let staticNetwork: null | Network = null;
try {
staticNetwork = Network.from(network);
} catch (error) { }
const providers: Array<AbstractProvider> = [ ];
if (allowService("publicPolygon") && staticNetwork) {
if (staticNetwork.name === "matic") {
providers.push(new JsonRpcProvider("https:/\/polygon-rpc.com/", staticNetwork, { staticNetwork }));
} else if (staticNetwork.name === "matic-amoy") {
providers.push(new JsonRpcProvider("https:/\/rpc-amoy.polygon.technology/", staticNetwork, { staticNetwork }));
}
}
if (allowService("alchemy")) {
try {
providers.push(new AlchemyProvider(network, options.alchemy));
} catch (error) { }
}
if (allowService("ankr") && options.ankr != null) {
try {
providers.push(new AnkrProvider(network, options.ankr));
} catch (error) { }
}
/* Temporarily remove until custom error issue is fixed
if (allowService("blockscout")) {
try {
providers.push(new BlockscoutProvider(network, options.blockscout));
} catch (error) { }
}
*/
if (allowService("chainstack")) {
try {
providers.push(new ChainstackProvider(network, options.chainstack));
} catch (error) { }
}
if (allowService("cloudflare")) {
try {
providers.push(new CloudflareProvider(network));
} catch (error) { }
}
if (allowService("etherscan")) {
try {
providers.push(new EtherscanProvider(network, options.etherscan));
} catch (error) { }
}
if (allowService("infura")) {
try {
let projectId = options.infura;
let projectSecret: undefined | string = undefined;
if (typeof(projectId) === "object") {
projectSecret = projectId.projectSecret;
projectId = projectId.projectId;
}
providers.push(new InfuraProvider(network, projectId, projectSecret));
} catch (error) { }
}
/*
if (options.pocket !== "-") {
try {
let appId = options.pocket;
let secretKey: undefined | string = undefined;
let loadBalancer: undefined | boolean = undefined;
if (typeof(appId) === "object") {
loadBalancer = !!appId.loadBalancer;
secretKey = appId.secretKey;
appId = appId.appId;
}
providers.push(new PocketProvider(network, appId, secretKey, loadBalancer));
} catch (error) { console.log(error); }
}
*/
if (allowService("quicknode")) {
try {
let token = options.quicknode;
providers.push(new QuickNodeProvider(network, token));
} catch (error) { }
}
assert(providers.length, "unsupported default network", "UNSUPPORTED_OPERATION", {
operation: "getDefaultProvider"
});
// No need for a FallbackProvider
if (providers.length === 1) { return providers[0]; }
// We use the floor because public third-party providers can be unreliable,
// so a low number of providers with a large quorum will fail too often
let quorum = Math.floor(providers.length / 2);
if (quorum > 2) { quorum = 2; }
// Testnets don't need as strong a security gaurantee and speed is
// more useful during testing
if (staticNetwork && Testnets.indexOf(staticNetwork.name) !== -1) { quorum = 1; }
// Provided override qorum takes priority
if (options && options.quorum) { quorum = options.quorum; }
return new FallbackProvider(providers, undefined, { quorum });
}

606
dev/env/node_modules/ethers/src.ts/providers/ens-resolver.ts generated vendored Executable file
View File

@@ -0,0 +1,606 @@
/**
* 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]
*/
import { getAddress } from "../address/index.js";
import { ZeroAddress } from "../constants/index.js";
import { Contract } from "../contract/index.js";
import { dnsEncode, namehash } from "../hash/index.js";
import {
hexlify, isHexString, toBeHex,
defineProperties, encodeBase58,
assert, assertArgument, isError,
FetchRequest
} from "../utils/index.js";
import type { FunctionFragment } from "../abi/index.js";
import type { BytesLike } from "../utils/index.js";
import type { AbstractProvider, AbstractProviderPlugin } from "./abstract-provider.js";
import type { EnsPlugin } from "./plugins-network.js";
import type { Provider } from "./provider.js";
// @TODO: This should use the fetch-data:ipfs gateway
// Trim off the ipfs:// prefix and return the default gateway URL
function getIpfsLink(link: string): string {
if (link.match(/^ipfs:\/\/ipfs\//i)) {
link = link.substring(12);
} else if (link.match(/^ipfs:\/\//i)) {
link = link.substring(7);
} else {
assertArgument(false, "unsupported IPFS format", "link", link);
}
return `https:/\/gateway.ipfs.io/ipfs/${ link }`;
}
/**
* The type of data found during a steip during avatar resolution.
*/
export type AvatarLinkageType = "name" | "avatar" | "!avatar" | "url" | "data" | "ipfs" |
"erc721" | "erc1155" | "!erc721-caip" | "!erc1155-caip" |
"!owner" | "owner" | "!balance" | "balance" |
"metadata-url-base" | "metadata-url-expanded" | "metadata-url" | "!metadata-url" |
"!metadata" | "metadata" |
"!imageUrl" | "imageUrl-ipfs" | "imageUrl" | "!imageUrl-ipfs";
/**
* An individual record for each step during avatar resolution.
*/
export interface AvatarLinkage {
/**
* The type of linkage.
*/
type: AvatarLinkageType;
/**
* The linkage value.
*/
value: string;
};
/**
* When resolving an avatar for an ENS name, there are many
* steps involved, fetching metadata, validating results, et cetera.
*
* Some applications may wish to analyse this data, or use this data
* to diagnose promblems, so an **AvatarResult** provides details of
* 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;
};
/**
* 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 });
}
connect(proivder: Provider): MulticoinProviderPlugin {
return this;
}
/**
* Returns ``true`` if %%coinType%% is supported by this plugin.
*/
supportsCoinType(coinType: number): boolean {
return false;
}
/**
* Resolves to the encoded %%address%% for %%coinType%%.
*/
async encodeAddress(coinType: number, address: string): Promise<string> {
throw new Error("unsupported coin");
}
/**
* Resolves to the decoded %%data%% for %%coinType%%.
*/
async decodeAddress(coinType: number, data: BytesLike): Promise<string> {
throw new Error("unsupported coin");
}
}
const BasicMulticoinPluginId = "org.ethers.plugins.provider.BasicMulticoin";
/**
* 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);
}
}
const matcherIpfs = new RegExp("^(ipfs):/\/(.*)$", "i");
const matchers = [
new RegExp("^(https):/\/(.*)$", "i"),
new RegExp("^(data):(.*)$", "i"),
matcherIpfs,
new RegExp("^eip155:[0-9]+/(erc[0-9]+):(.*)$", "i"),
];
/**
* A connected object to a resolved ENS name resolver, which can be
* used to query additional details.
*/
export class EnsResolver {
/**
* The connected provider.
*/
provider!: AbstractProvider;
/**
* The address of the resolver.
*/
address!: string;
/**
* The name this resolver was resolved against.
*/
name!: string;
// For EIP-2544 names, the ancestor that provided the resolver
#supports2544: null | Promise<boolean>;
#resolver: Contract;
constructor(provider: AbstractProvider, address: string, name: string) {
defineProperties<EnsResolver>(this, { provider, address, name });
this.#supports2544 = null;
this.#resolver = new Contract(address, [
"function supportsInterface(bytes4) view returns (bool)",
"function resolve(bytes, bytes) view returns (bytes)",
"function addr(bytes32) view returns (address)",
"function addr(bytes32, uint) view returns (bytes)",
"function text(bytes32, string) view returns (string)",
"function contenthash(bytes32) view returns (bytes)",
], provider);
}
/**
* Resolves to true if the resolver supports wildcard resolution.
*/
async supportsWildcard(): Promise<boolean> {
if (this.#supports2544 == null) {
this.#supports2544 = (async () => {
try {
return await this.#resolver.supportsInterface("0x9061b923");
} catch (error) {
// Wildcard resolvers must understand supportsInterface
// and return true.
if (isError(error, "CALL_EXCEPTION")) { return false; }
// Let future attempts try again...
this.#supports2544 = null;
throw error;
}
})();
}
return await this.#supports2544;
}
async #fetch(funcName: string, params?: Array<any>): Promise<null | any> {
params = (params || []).slice();
const iface = this.#resolver.interface;
// The first parameters is always the nodehash
params.unshift(namehash(this.name))
let fragment: null | FunctionFragment = null;
if (await this.supportsWildcard()) {
fragment = iface.getFunction(funcName);
assert(fragment, "missing fragment", "UNKNOWN_ERROR", {
info: { funcName }
});
params = [
dnsEncode(this.name, 255),
iface.encodeFunctionData(fragment, params)
];
funcName = "resolve(bytes,bytes)";
}
params.push({
enableCcipRead: true
});
try {
const result = await this.#resolver[funcName](...params);
if (fragment) {
return iface.decodeFunctionResult(fragment, result)[0];
}
return result;
} catch (error: any) {
if (!isError(error, "CALL_EXCEPTION")) { throw error; }
}
return null;
}
/**
* Resolves to the address for %%coinType%% or null if the
* provided %%coinType%% has not been configured.
*/
async getAddress(coinType?: number): Promise<null | string> {
if (coinType == null) { coinType = 60; }
if (coinType === 60) {
try {
const result = await this.#fetch("addr(bytes32)");
// No address
if (result == null || result === ZeroAddress) { return null; }
return result;
} catch (error: any) {
if (isError(error, "CALL_EXCEPTION")) { return null; }
throw error;
}
}
// Try decoding its EVM canonical chain as an EVM chain address first
if (coinType >= 0 && coinType < 0x80000000) {
let ethCoinType = coinType + 0x80000000;
const data = await this.#fetch("addr(bytes32,uint)", [ ethCoinType ]);
if (isHexString(data, 20)) { return getAddress(data); }
}
let coinPlugin: null | MulticoinProviderPlugin = null;
for (const plugin of this.provider.plugins) {
if (!(plugin instanceof MulticoinProviderPlugin)) { continue; }
if (plugin.supportsCoinType(coinType)) {
coinPlugin = plugin;
break;
}
}
if (coinPlugin == null) { return null; }
// keccak256("addr(bytes32,uint256")
const data = await this.#fetch("addr(bytes32,uint)", [ coinType ]);
// No address
if (data == null || data === "0x") { return null; }
// Compute the address
const address = await coinPlugin.decodeAddress(coinType, data);
if (address != null) { return address; }
assert(false, `invalid coin data`, "UNSUPPORTED_OPERATION", {
operation: `getAddress(${ coinType })`,
info: { coinType, data }
});
}
/**
* Resolves to the EIP-634 text record for %%key%%, or ``null``
* if unconfigured.
*/
async getText(key: string): Promise<null | string> {
const data = await this.#fetch("text(bytes32,string)", [ key ]);
if (data == null || data === "0x") { return null; }
return data;
}
/**
* Rsolves to the content-hash or ``null`` if unconfigured.
*/
async getContentHash(): Promise<null | string> {
// keccak256("contenthash()")
const data = await this.#fetch("contenthash(bytes32)");
// No contenthash
if (data == null || data === "0x") { return null; }
// IPFS (CID: 1, Type: 70=DAG-PB, 72=libp2p-key)
const ipfs = data.match(/^0x(e3010170|e5010172)(([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f]*))$/);
if (ipfs) {
const scheme = (ipfs[1] === "e3010170") ? "ipfs": "ipns";
const length = parseInt(ipfs[4], 16);
if (ipfs[5].length === length * 2) {
return `${ scheme }:/\/${ encodeBase58("0x" + ipfs[2])}`;
}
}
// Swarm (CID: 1, Type: swarm-manifest; hash/length hard-coded to keccak256/32)
const swarm = data.match(/^0xe40101fa011b20([0-9a-f]*)$/)
if (swarm && swarm[1].length === 64) {
return `bzz:/\/${ swarm[1] }`;
}
assert(false, `invalid or unsupported content hash data`, "UNSUPPORTED_OPERATION", {
operation: "getContentHash()",
info: { data }
});
}
/**
* Resolves to the avatar url or ``null`` if the avatar is either
* unconfigured or incorrectly configured (e.g. references an NFT
* not owned by the address).
*
* If diagnosing issues with configurations, the [[_getAvatar]]
* method may be useful.
*/
async getAvatar(): Promise<null | string> {
const avatar = await this._getAvatar();
return avatar.url;
}
/**
* When resolving an avatar, there are many steps involved, such
* fetching metadata and possibly validating ownership of an
* NFT.
*
* This method can be used to examine each step and the value it
* was working from.
*/
async _getAvatar(): Promise<AvatarResult> {
const linkage: Array<AvatarLinkage> = [ { type: "name", value: this.name } ];
try {
// test data for ricmoo.eth
//const avatar = "eip155:1/erc721:0x265385c7f4132228A0d54EB1A9e7460b91c0cC68/29233";
const avatar = await this.getText("avatar");
if (avatar == null) {
linkage.push({ type: "!avatar", value: "" });
return { url: null, linkage };
}
linkage.push({ type: "avatar", value: avatar });
for (let i = 0; i < matchers.length; i++) {
const match = avatar.match(matchers[i]);
if (match == null) { continue; }
const scheme = match[1].toLowerCase();
switch (scheme) {
case "https":
case "data":
linkage.push({ type: "url", value: avatar });
return { linkage, url: avatar };
case "ipfs": {
const url = getIpfsLink(avatar);
linkage.push({ type: "ipfs", value: avatar });
linkage.push({ type: "url", value: url });
return { linkage, url };
}
case "erc721":
case "erc1155": {
// Depending on the ERC type, use tokenURI(uint256) or url(uint256)
const selector = (scheme === "erc721") ? "tokenURI(uint256)": "uri(uint256)";
linkage.push({ type: scheme, value: avatar });
// The owner of this name
const owner = await this.getAddress();
if (owner == null) {
linkage.push({ type: "!owner", value: "" });
return { url: null, linkage };
}
const comps = (match[2] || "").split("/");
if (comps.length !== 2) {
linkage.push({ type: <any>`!${ scheme }caip`, value: (match[2] || "") });
return { url: null, linkage };
}
const tokenId = comps[1];
const contract = new Contract(comps[0], [
// ERC-721
"function tokenURI(uint) view returns (string)",
"function ownerOf(uint) view returns (address)",
// ERC-1155
"function uri(uint) view returns (string)",
"function balanceOf(address, uint256) view returns (uint)"
], this.provider);
// Check that this account owns the token
if (scheme === "erc721") {
const tokenOwner = await contract.ownerOf(tokenId);
if (owner !== tokenOwner) {
linkage.push({ type: "!owner", value: tokenOwner });
return { url: null, linkage };
}
linkage.push({ type: "owner", value: tokenOwner });
} else if (scheme === "erc1155") {
const balance = await contract.balanceOf(owner, tokenId);
if (!balance) {
linkage.push({ type: "!balance", value: "0" });
return { url: null, linkage };
}
linkage.push({ type: "balance", value: balance.toString() });
}
// Call the token contract for the metadata URL
let metadataUrl = await contract[selector](tokenId);
if (metadataUrl == null || metadataUrl === "0x") {
linkage.push({ type: "!metadata-url", value: "" });
return { url: null, linkage };
}
linkage.push({ type: "metadata-url-base", value: metadataUrl });
// ERC-1155 allows a generic {id} in the URL
if (scheme === "erc1155") {
metadataUrl = metadataUrl.replace("{id}", toBeHex(tokenId, 32).substring(2));
linkage.push({ type: "metadata-url-expanded", value: metadataUrl });
}
// Transform IPFS metadata links
if (metadataUrl.match(/^ipfs:/i)) {
metadataUrl = getIpfsLink(metadataUrl);
}
linkage.push({ type: "metadata-url", value: metadataUrl });
// Get the token metadata
let metadata: any = { };
const response = await (new FetchRequest(metadataUrl)).send();
response.assertOk();
try {
metadata = response.bodyJson;
} catch (error) {
try {
linkage.push({ type: "!metadata", value: response.bodyText });
} catch (error) {
const bytes = response.body;
if (bytes) {
linkage.push({ type: "!metadata", value: hexlify(bytes) });
}
return { url: null, linkage };
}
return { url: null, linkage };
}
if (!metadata) {
linkage.push({ type: "!metadata", value: "" });
return { url: null, linkage };
}
linkage.push({ type: "metadata", value: JSON.stringify(metadata) });
// Pull the image URL out
let imageUrl = metadata.image;
if (typeof(imageUrl) !== "string") {
linkage.push({ type: "!imageUrl", value: "" });
return { url: null, linkage };
}
if (imageUrl.match(/^(https:\/\/|data:)/i)) {
// Allow
} else {
// Transform IPFS link to gateway
const ipfs = imageUrl.match(matcherIpfs);
if (ipfs == null) {
linkage.push({ type: "!imageUrl-ipfs", value: imageUrl });
return { url: null, linkage };
}
linkage.push({ type: "imageUrl-ipfs", value: imageUrl });
imageUrl = getIpfsLink(imageUrl);
}
linkage.push({ type: "url", value: imageUrl });
return { linkage, url: imageUrl };
}
}
}
} catch (error) { }
return { linkage, url: null };
}
static async getEnsAddress(provider: Provider): Promise<string> {
const network = await provider.getNetwork();
const ensPlugin = network.getPlugin<EnsPlugin>("org.ethers.plugins.network.Ens");
// No ENS...
assert(ensPlugin, "network does not support ENS", "UNSUPPORTED_OPERATION", {
operation: "getEnsAddress", info: { network } });
return ensPlugin.address;
}
static async #getResolver(provider: Provider, name: string): Promise<null | string> {
const ensAddr = await EnsResolver.getEnsAddress(provider);
try {
const contract = new Contract(ensAddr, [
"function resolver(bytes32) view returns (address)"
], provider);
const addr = await contract.resolver(namehash(name), {
enableCcipRead: true
});
if (addr === ZeroAddress) { return null; }
return addr;
} catch (error) {
// ENS registry cannot throw errors on resolver(bytes32),
// so probably a link error
throw error;
}
return null;
}
/**
* Resolve to the ENS resolver for %%name%% using %%provider%% or
* ``null`` if unconfigured.
*/
static async fromName(provider: AbstractProvider, name: string): Promise<null | EnsResolver> {
let currentName = name;
while (true) {
if (currentName === "" || currentName === ".") { return null; }
// Optimization since the eth node cannot change and does
// not have a wildcard resolver
if (name !== "eth" && currentName === "eth") { return null; }
// Check the current node for a resolver
const addr = await EnsResolver.#getResolver(provider, currentName);
// Found a resolver!
if (addr != null) {
const resolver = new EnsResolver(provider, addr, name);
// Legacy resolver found, using EIP-2544 so it isn't safe to use
if (currentName !== name && !(await resolver.supportsWildcard())) { return null; }
return resolver;
}
// Get the parent node
currentName = currentName.split(".").slice(1).join(".");
}
}
}

335
dev/env/node_modules/ethers/src.ts/providers/format.ts generated vendored Executable file
View File

@@ -0,0 +1,335 @@
/**
* @_ignore
*/
import { getAddress, getCreateAddress } from "../address/index.js";
import { Signature } from "../crypto/index.js"
import { accessListify } from "../transaction/index.js";
import {
getBigInt, getNumber, hexlify, isHexString, zeroPadValue,
assert, assertArgument
} from "../utils/index.js";
import type { SignatureLike } from "../crypto/index.js"
import type {
BlockParams, LogParams,
TransactionReceiptParams, TransactionResponseParams,
} from "./formatting.js";
const BN_0 = BigInt(0);
export type FormatFunc = (value: any) => any;
export function allowNull(format: FormatFunc, nullValue?: any): FormatFunc {
return (function(value: any) {
if (value == null) { return nullValue; }
return format(value);
});
}
export function arrayOf(format: FormatFunc, allowNull?: boolean): FormatFunc {
return ((array: any) => {
if (allowNull && array == null) { return null; }
if (!Array.isArray(array)) { throw new Error("not an array"); }
return array.map((i) => format(i));
});
}
// Requires an object which matches a fleet of other formatters
// Any FormatFunc may return `undefined` to have the value omitted
// from the result object. Calls preserve `this`.
export function object(format: Record<string, FormatFunc>, altNames?: Record<string, Array<string>>): FormatFunc {
return ((value: any) => {
const result: any = { };
for (const key in format) {
let srcKey = key;
if (altNames && key in altNames && !(srcKey in value)) {
for (const altKey of altNames[key]) {
if (altKey in value) {
srcKey = altKey;
break;
}
}
}
try {
const nv = format[key](value[srcKey]);
if (nv !== undefined) { result[key] = nv; }
} catch (error) {
const message = (error instanceof Error) ? error.message: "not-an-error";
assert(false, `invalid value for value.${ key } (${ message })`, "BAD_DATA", { value })
}
}
return result;
});
}
export function formatBoolean(value: any): boolean {
switch (value) {
case true: case "true":
return true;
case false: case "false":
return false;
}
assertArgument(false, `invalid boolean; ${ JSON.stringify(value) }`, "value", value);
}
export function formatData(value: string): string {
assertArgument(isHexString(value, true), "invalid data", "value", value);
return value;
}
export function formatHash(value: any): string {
assertArgument(isHexString(value, 32), "invalid hash", "value", value);
return value;
}
export function formatUint256(value: any): string {
if (!isHexString(value)) {
throw new Error("invalid uint256");
}
return zeroPadValue(value, 32);
}
const _formatLog = object({
address: getAddress,
blockHash: formatHash,
blockNumber: getNumber,
data: formatData,
index: getNumber,
removed: allowNull(formatBoolean, false),
topics: arrayOf(formatHash),
transactionHash: formatHash,
transactionIndex: getNumber,
}, {
index: [ "logIndex" ]
});
export function formatLog(value: any): LogParams {
return _formatLog(value);
}
const _formatBlock = object({
hash: allowNull(formatHash),
parentHash: formatHash,
parentBeaconBlockRoot: allowNull(formatHash, null),
number: getNumber,
timestamp: getNumber,
nonce: allowNull(formatData),
difficulty: getBigInt,
gasLimit: getBigInt,
gasUsed: getBigInt,
stateRoot: allowNull(formatHash, null),
receiptsRoot: allowNull(formatHash, null),
blobGasUsed: allowNull(getBigInt, null),
excessBlobGas: allowNull(getBigInt, null),
miner: allowNull(getAddress),
prevRandao: allowNull(formatHash, null),
extraData: formatData,
baseFeePerGas: allowNull(getBigInt)
}, {
prevRandao: [ "mixHash" ]
});
export function formatBlock(value: any): BlockParams {
const result = _formatBlock(value);
result.transactions = value.transactions.map((tx: string | TransactionResponseParams) => {
if (typeof(tx) === "string") { return tx; }
return formatTransactionResponse(tx);
});
return result;
}
const _formatReceiptLog = object({
transactionIndex: getNumber,
blockNumber: getNumber,
transactionHash: formatHash,
address: getAddress,
topics: arrayOf(formatHash),
data: formatData,
index: getNumber,
blockHash: formatHash,
}, {
index: [ "logIndex" ]
});
export function formatReceiptLog(value: any): LogParams {
return _formatReceiptLog(value);
}
const _formatTransactionReceipt = object({
to: allowNull(getAddress, null),
from: allowNull(getAddress, null),
contractAddress: allowNull(getAddress, null),
// should be allowNull(hash), but broken-EIP-658 support is handled in receipt
index: getNumber,
root: allowNull(hexlify),
gasUsed: getBigInt,
blobGasUsed: allowNull(getBigInt, null),
logsBloom: allowNull(formatData),
blockHash: formatHash,
hash: formatHash,
logs: arrayOf(formatReceiptLog),
blockNumber: getNumber,
//confirmations: allowNull(getNumber, null),
cumulativeGasUsed: getBigInt,
effectiveGasPrice: allowNull(getBigInt),
blobGasPrice: allowNull(getBigInt, null),
status: allowNull(getNumber),
type: allowNull(getNumber, 0)
}, {
effectiveGasPrice: [ "gasPrice" ],
hash: [ "transactionHash" ],
index: [ "transactionIndex" ],
});
export function formatTransactionReceipt(value: any): TransactionReceiptParams {
return _formatTransactionReceipt(value);
}
export function formatTransactionResponse(value: any): TransactionResponseParams {
// Some clients (TestRPC) do strange things like return 0x0 for the
// 0 address; correct this to be a real address
if (value.to && getBigInt(value.to) === BN_0) {
value.to = "0x0000000000000000000000000000000000000000";
}
const result = object({
hash: formatHash,
// Some nodes do not return this, usually test nodes (like Ganache)
index: allowNull(getNumber, undefined),
type: (value: any) => {
if (value === "0x" || value == null) { return 0; }
return getNumber(value);
},
accessList: allowNull(accessListify, null),
blobVersionedHashes: allowNull(arrayOf(formatHash, true), null),
authorizationList: allowNull(arrayOf((v: any) => {
let sig: SignatureLike;
if (v.signature) {
sig = v.signature;
} else {
let yParity = v.yParity;
if (yParity === "0x1b") {
yParity = 0;
} else if (yParity === "0x1c") {
yParity = 1;
}
sig = Object.assign({ }, v, { yParity });
}
return {
address: getAddress(v.address),
chainId: getBigInt(v.chainId),
nonce: getBigInt(v.nonce),
signature: Signature.from(sig)
};
}, false), null),
blockHash: allowNull(formatHash, null),
blockNumber: allowNull(getNumber, null),
transactionIndex: allowNull(getNumber, null),
from: getAddress,
// either (gasPrice) or (maxPriorityFeePerGas + maxFeePerGas) must be set
gasPrice: allowNull(getBigInt),
maxPriorityFeePerGas: allowNull(getBigInt),
maxFeePerGas: allowNull(getBigInt),
maxFeePerBlobGas: allowNull(getBigInt, null),
gasLimit: getBigInt,
to: allowNull(getAddress, null),
value: getBigInt,
nonce: getNumber,
data: formatData,
creates: allowNull(getAddress, null),
chainId: allowNull(getBigInt, null)
}, {
data: [ "input" ],
gasLimit: [ "gas" ],
index: [ "transactionIndex" ]
})(value);
// If to and creates are empty, populate the creates from the value
if (result.to == null && result.creates == null) {
result.creates = getCreateAddress(result);
}
// @TODO: Check fee data
// Add an access list to supported transaction types
if ((value.type === 1 || value.type === 2) && value.accessList == null) {
result.accessList = [ ];
}
// Compute the signature
if (value.signature) {
result.signature = Signature.from(value.signature);
} else {
result.signature = Signature.from(value);
}
// Some backends omit ChainId on legacy transactions, but we can compute it
if (result.chainId == null) {
const chainId = result.signature.legacyChainId;
if (chainId != null) { result.chainId = chainId; }
}
// @TODO: check chainID
/*
if (value.chainId != null) {
let chainId = value.chainId;
if (isHexString(chainId)) {
chainId = BigNumber.from(chainId).toNumber();
}
result.chainId = chainId;
} else {
let chainId = value.networkId;
// geth-etc returns chainId
if (chainId == null && result.v == null) {
chainId = value.chainId;
}
if (isHexString(chainId)) {
chainId = BigNumber.from(chainId).toNumber();
}
if (typeof(chainId) !== "number" && result.v != null) {
chainId = (result.v - 35) / 2;
if (chainId < 0) { chainId = 0; }
chainId = parseInt(chainId);
}
if (typeof(chainId) !== "number") { chainId = 0; }
result.chainId = chainId;
}
*/
// 0x0000... should actually be null
if (result.blockHash && getBigInt(result.blockHash) === BN_0) {
result.blockHash = null;
}
return result;
}

418
dev/env/node_modules/ethers/src.ts/providers/formatting.ts generated vendored Executable file
View File

@@ -0,0 +1,418 @@
/**
* About provider formatting?
*
* @_section: api/providers/formatting:Formatting [provider-formatting]
*/
import type { Signature } from "../crypto/index.js";
import type { Authorization, AccessList } from "../transaction/index.js";
//////////////////////
// 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;
/**
* The hash tree root of the parent beacon block for the given
* execution block. See [[link-eip-4788]].
*/
parentBeaconBlockRoot?: null | 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 an 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 total amount of BLOb gas consumed by transactions within
* the block. See [[link-eip4844].
*/
blobGasUsed?: null | bigint;
/**
* The running total of BLOb gas consumed in excess of the target
* prior to the block. See [[link-eip-4844]].
*/
excessBlobGas?: null | bigint;
/**
* The miner (or author) of a block.
*/
miner: string;
/**
* The latest RANDAO mix of the post beacon state of
* the previous block.
*/
prevRandao?: null | 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 root hash for the global state after applying changes
* in this block.
*/
stateRoot?: null | string;
/**
* The hash of the transaction receipts trie.
*/
receiptsRoot?: null | string;
/**
* The list of transactions in the block.
*/
transactions: ReadonlyArray<string | TransactionResponseParams>;
};
//////////////////////
// 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;
}
//////////////////////
// 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 transaction.
*/
gasUsed: bigint;
/**
* The amount of BLOb gas used. See [[link-eip-4844]].
*/
blobGasUsed?: null | 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 BLOb gas price that was charged. See [[link-eip-4844]].
*/
blobGasPrice?: 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;
}
/*
export interface LegacyTransactionReceipt {
byzantium: false;
status: null;
root: string;
}
export interface ByzantiumTransactionReceipt {
byzantium: true;
status: number;
root: null;
}
*/
//////////////////////
// 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;
/**
* For [[link-eip-4844]] transactions, this is the maximum fee that
* will be paid per BLOb.
*/
maxFeePerBlobGas?: 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;
/**
* The [[link-eip-4844]] BLOb versioned hashes.
*/
blobVersionedHashes?: null | Array<string>; // @TODO: drop the "?"? (v7)
/**
* The [[link-eip-7702]] authorizations (if any).
*/
authorizationList: null | Array<Authorization>;
};

135
dev/env/node_modules/ethers/src.ts/providers/index.ts generated vendored Executable file
View File

@@ -0,0 +1,135 @@
/**
* 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";
export {
AbstractSigner,
VoidSigner,
} from "./abstract-signer.js";
export {
showThrottleMessage
} from "./community.js";
export { getDefaultProvider } from "./default-provider.js";
export {
EnsResolver,
MulticoinProviderPlugin
} from "./ens-resolver.js";
export { Network } from "./network.js";
export { NonceManager } from "./signer-noncemanager.js";
export {
NetworkPlugin,
GasCostPlugin,
EnsPlugin,
FeeDataNetworkPlugin,
FetchUrlFeeDataNetworkPlugin,
} from "./plugins-network.js";
export {
Block,
FeeData,
Log,
TransactionReceipt,
TransactionResponse,
copyRequest,
//resolveTransactionRequest,
} from "./provider.js";
export { FallbackProvider } from "./provider-fallback.js";
export { JsonRpcApiProvider, JsonRpcProvider, JsonRpcSigner } from "./provider-jsonrpc.js"
export { BrowserProvider } from "./provider-browser.js";
export { AlchemyProvider } from "./provider-alchemy.js";
export { BlockscoutProvider } from "./provider-blockscout.js";
export { AnkrProvider } from "./provider-ankr.js";
export { CloudflareProvider } from "./provider-cloudflare.js";
export { ChainstackProvider } from "./provider-chainstack.js";
export { EtherscanProvider, EtherscanPlugin } from "./provider-etherscan.js";
export { InfuraProvider, InfuraWebSocketProvider } from "./provider-infura.js";
export { PocketProvider } from "./provider-pocket.js";
export { QuickNodeProvider } from "./provider-quicknode.js";
import { IpcSocketProvider } from "./provider-ipcsocket.js"; /*-browser*/
export { IpcSocketProvider };
export { SocketProvider } from "./provider-socket.js";
export { WebSocketProvider } from "./provider-websocket.js";
export {
SocketSubscriber, SocketBlockSubscriber, SocketPendingSubscriber,
SocketEventSubscriber
} from "./provider-socket.js";
export type {
AbstractProviderOptions, Subscription, Subscriber,
AbstractProviderPlugin,
PerformActionFilter, PerformActionTransaction, PerformActionRequest,
} from "./abstract-provider.js"
export type { ContractRunner } from "./contracts.js";
export type {
BlockParams, LogParams, TransactionReceiptParams,
TransactionResponseParams,
} from "./formatting.js";
export type {
CommunityResourcable
} from "./community.js";
/*
export type {
AvatarLinkageType, AvatarLinkage, AvatarResult
} from "./ens-resolver.js";
*/
export type { Networkish } from "./network.js";
export type { GasCostParameters } from "./plugins-network.js";
export type {
BlockTag,
TransactionRequest, PreparedTransactionRequest,
EventFilter, Filter, FilterByBlockHash, OrphanFilter, ProviderEvent,
TopicFilter,
Provider,
MinedBlock, MinedTransactionResponse
} from "./provider.js";
export type {
BrowserDiscoverOptions, BrowserProviderOptions, DebugEventBrowserProvider,
Eip1193Provider, Eip6963ProviderInfo
} from "./provider-browser.js";
export type { FallbackProviderOptions } from "./provider-fallback.js";
export type {
JsonRpcPayload, JsonRpcResult, JsonRpcError,
JsonRpcApiProviderOptions,
JsonRpcTransactionRequest,
} from "./provider-jsonrpc.js";
export type {
WebSocketCreator, WebSocketLike
} from "./provider-websocket.js";
export type { Signer } from "./signer.js";

438
dev/env/node_modules/ethers/src.ts/providers/network.ts generated vendored Executable file
View File

@@ -0,0 +1,438 @@
/**
* A **Network** encapsulates the various properties required to
* interact with a specific chain.
*
* @_subsection: api/providers:Networks [networks]
*/
import { accessListify } from "../transaction/index.js";
import { getBigInt, assert, assertArgument } from "../utils/index.js";
import {
EnsPlugin, FetchUrlFeeDataNetworkPlugin, GasCostPlugin
} from "./plugins-network.js";
import type { BigNumberish } from "../utils/index.js";
import type { TransactionLike } from "../transaction/index.js";
import type { NetworkPlugin } from "./plugins-network.js";
/**
* A Networkish can be used to allude to a Network, by specifing:
* - a [[Network]] object
* - a well-known (or registered) network name
* - a well-known (or registered) chain ID
* - an object with sufficient details to describe a network
*/
export type Networkish = Network | number | bigint | string | {
name?: string,
chainId?: number,
//layerOneConnection?: Provider,
ensAddress?: string,
ensNetwork?: number
};
/* * * *
// Networks which operation against an L2 can use this plugin to
// specify how to access L1, for the purpose of resolving ENS,
// for example.
export class LayerOneConnectionPlugin extends NetworkPlugin {
readonly provider!: Provider;
// @TODO: Rename to ChainAccess and allow for connecting to any chain
constructor(provider: Provider) {
super("org.ethers.plugins.layer-one-connection");
defineProperties<LayerOneConnectionPlugin>(this, { provider });
}
clone(): LayerOneConnectionPlugin {
return new LayerOneConnectionPlugin(this.provider);
}
}
*/
const Networks: Map<string | bigint, () => Network> = new Map();
/**
* 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) };
}
/**
* The network common name.
*
* This is the canonical name, as networks migh have multiple
* names.
*/
get name(): string { return this.#name; }
set name(value: string) { this.#name = value; }
/**
* The network chain ID.
*/
get chainId(): bigint { return this.#chainId; }
set chainId(value: BigNumberish) { this.#chainId = getBigInt(value, "chainId"); }
/**
* Returns true if %%other%% matches this network. Any chain ID
* must match, and if no chain ID is present, the name must match.
*
* This method does not currently check for additional properties,
* such as ENS address or plug-in compatibility.
*/
matches(other: Networkish): boolean {
if (other == null) { return false; }
if (typeof(other) === "string") {
try {
return (this.chainId === getBigInt(other));
} catch (error) { }
return (this.name === other);
}
if (typeof(other) === "number" || typeof(other) === "bigint") {
try {
return (this.chainId === getBigInt(other));
} catch (error) { }
return false;
}
if (typeof(other) === "object") {
if (other.chainId != null) {
try {
return (this.chainId === getBigInt(other.chainId));
} catch (error) { }
return false;
}
if (other.name != null) {
return (this.name === other.name);
}
return false;
}
return false;
}
/**
* Returns the list of plugins currently attached to this Network.
*/
get plugins(): Array<NetworkPlugin> {
return Array.from(this.#plugins.values());
}
/**
* Attach a new %%plugin%% to this Network. The network name
* must be unique, excluding any fragment.
*/
attachPlugin(plugin: NetworkPlugin): this {
if (this.#plugins.get(plugin.name)) {
throw new Error(`cannot replace existing plugin: ${ plugin.name } `);
}
this.#plugins.set(plugin.name, plugin.clone());
return this;
}
/**
* Return the plugin, if any, matching %%name%% exactly. Plugins
* with fragments will not be returned unless %%name%% includes
* a fragment.
*/
getPlugin<T extends NetworkPlugin = NetworkPlugin>(name: string): null | T {
return <T>(this.#plugins.get(name)) || null;
}
/**
* Gets a list of all plugins that match %%name%%, with otr without
* a fragment.
*/
getPlugins<T extends NetworkPlugin = NetworkPlugin>(basename: string): Array<T> {
return <Array<T>>(this.plugins.filter((p) => (p.name.split("#")[0] === basename)));
}
/**
* Create a copy of this Network.
*/
clone(): Network {
const clone = new Network(this.name, this.chainId);
this.plugins.forEach((plugin) => {
clone.attachPlugin(plugin.clone());
});
return clone;
}
/**
* Compute the intrinsic gas required for a transaction.
*
* A GasCostPlugin can be attached to override the default
* values.
*/
computeIntrinsicGas(tx: TransactionLike): number {
const costs = this.getPlugin<GasCostPlugin>("org.ethers.plugins.network.GasCost") || (new GasCostPlugin());
let gas = costs.txBase;
if (tx.to == null) { gas += costs.txCreate; }
if (tx.data) {
for (let i = 2; i < tx.data.length; i += 2) {
if (tx.data.substring(i, i + 2) === "00") {
gas += costs.txDataZero;
} else {
gas += costs.txDataNonzero;
}
}
}
if (tx.accessList) {
const accessList = accessListify(tx.accessList);
for (const addr in accessList) {
gas += costs.txAccessListAddress + costs.txAccessListStorageKey * accessList[addr].storageKeys.length;
}
}
return gas;
}
/**
* Returns a new Network for the %%network%% name or chainId.
*/
static from(network?: Networkish): Network {
injectCommonNetworks();
// Default network
if (network == null) { return Network.from("mainnet"); }
// Canonical name or chain ID
if (typeof(network) === "number") { network = BigInt(network); }
if (typeof(network) === "string" || typeof(network) === "bigint") {
const networkFunc = Networks.get(network);
if (networkFunc) { return networkFunc(); }
if (typeof(network) === "bigint") {
return new Network("unknown", network);
}
assertArgument(false, "unknown network", "network", network);
}
// Clonable with network-like abilities
if (typeof((<Network>network).clone) === "function") {
const clone = (<Network>network).clone();
//if (typeof(network.name) !== "string" || typeof(network.chainId) !== "number") {
//}
return clone;
}
// Networkish
if (typeof(network) === "object") {
assertArgument(typeof(network.name) === "string" && typeof(network.chainId) === "number",
"invalid network object name or chainId", "network", network);
const custom = new Network(<string>(network.name), <number>(network.chainId));
if ((<any>network).ensAddress || (<any>network).ensNetwork != null) {
custom.attachPlugin(new EnsPlugin((<any>network).ensAddress, (<any>network).ensNetwork));
}
//if ((<any>network).layerOneConnection) {
// custom.attachPlugin(new LayerOneConnectionPlugin((<any>network).layerOneConnection));
//}
return custom;
}
assertArgument(false, "invalid network", "network", network);
}
/**
* Register %%nameOrChainId%% with a function which returns
* an instance of a Network representing that chain.
*/
static register(nameOrChainId: string | number | bigint, networkFunc: () => Network): void {
if (typeof(nameOrChainId) === "number") { nameOrChainId = BigInt(nameOrChainId); }
const existing = Networks.get(nameOrChainId);
if (existing) {
assertArgument(false, `conflicting network for ${ JSON.stringify(existing.name) }`, "nameOrChainId", nameOrChainId);
}
Networks.set(nameOrChainId, networkFunc);
}
}
type Options = {
ensNetwork?: number;
altNames?: Array<string>;
plugins?: Array<NetworkPlugin>;
};
// We don't want to bring in formatUnits because it is backed by
// FixedNumber and we want to keep Networks tiny. The values
// included by the Gas Stations are also IEEE 754 with lots of
// rounding issues and exceed the strict checks formatUnits has.
function parseUnits(_value: number | string, decimals: number): bigint {
const value = String(_value);
if (!value.match(/^[0-9.]+$/)) {
throw new Error(`invalid gwei value: ${ _value }`);
}
// Break into [ whole, fraction ]
const comps = value.split(".");
if (comps.length === 1) { comps.push(""); }
// More than 1 decimal point or too many fractional positions
if (comps.length !== 2) {
throw new Error(`invalid gwei value: ${ _value }`);
}
// Pad the fraction to 9 decimalplaces
while (comps[1].length < decimals) { comps[1] += "0"; }
// Too many decimals and some non-zero ending, take the ceiling
if (comps[1].length > 9) {
let frac = BigInt(comps[1].substring(0, 9));
if (!comps[1].substring(9).match(/^0+$/)) { frac++; }
comps[1] = frac.toString();
}
return BigInt(comps[0] + comps[1]);
}
// Used by Polygon to use a gas station for fee data
function getGasStationPlugin(url: string) {
return new FetchUrlFeeDataNetworkPlugin(url, async (fetchFeeData, provider, request) => {
// Prevent Cloudflare from blocking our request in node.js
request.setHeader("User-Agent", "ethers");
let response;
try {
const [ _response, _feeData ] = await Promise.all([
request.send(), fetchFeeData()
]);
response = _response;
const payload = response.bodyJson.standard;
const feeData = {
gasPrice: _feeData.gasPrice,
maxFeePerGas: parseUnits(payload.maxFee, 9),
maxPriorityFeePerGas: parseUnits(payload.maxPriorityFee, 9),
};
return feeData;
} catch (error: any) {
assert(false, `error encountered with polygon gas station (${ JSON.stringify(request.url) })`, "SERVER_ERROR", { request, response, error });
}
});
}
// See: https://chainlist.org
let injected = false;
function injectCommonNetworks(): void {
if (injected) { return; }
injected = true;
/// Register popular Ethereum networks
function registerEth(name: string, chainId: number, options: Options): void {
const func = function() {
const network = new Network(name, chainId);
// We use 0 to disable ENS
if (options.ensNetwork != null) {
network.attachPlugin(new EnsPlugin(null, options.ensNetwork));
}
network.attachPlugin(new GasCostPlugin());
(options.plugins || []).forEach((plugin) => {
network.attachPlugin(plugin);
});
return network;
};
// Register the network by name and chain ID
Network.register(name, func);
Network.register(chainId, func);
if (options.altNames) {
options.altNames.forEach((name) => {
Network.register(name, func);
});
}
}
registerEth("mainnet", 1, { ensNetwork: 1, altNames: [ "homestead" ] });
registerEth("ropsten", 3, { ensNetwork: 3 });
registerEth("rinkeby", 4, { ensNetwork: 4 });
registerEth("goerli", 5, { ensNetwork: 5 });
registerEth("kovan", 42, { ensNetwork: 42 });
registerEth("sepolia", 11155111, { ensNetwork: 11155111 });
registerEth("holesky", 17000, { ensNetwork: 17000 });
registerEth("classic", 61, { });
registerEth("classicKotti", 6, { });
registerEth("arbitrum", 42161, {
ensNetwork: 1,
});
registerEth("arbitrum-goerli", 421613, { });
registerEth("arbitrum-sepolia", 421614, { });
registerEth("base", 8453, { ensNetwork: 1 });
registerEth("base-goerli", 84531, { });
registerEth("base-sepolia", 84532, { });
registerEth("bnb", 56, { ensNetwork: 1 });
registerEth("bnbt", 97, { });
registerEth("filecoin", 314, { });
registerEth("filecoin-calibration", 314159, { });
registerEth("linea", 59144, { ensNetwork: 1 });
registerEth("linea-goerli", 59140, { });
registerEth("linea-sepolia", 59141, { });
registerEth("matic", 137, {
ensNetwork: 1,
plugins: [
getGasStationPlugin("https:/\/gasstation.polygon.technology/v2")
]
});
registerEth("matic-amoy", 80002, { });
registerEth("matic-mumbai", 80001, {
altNames: [ "maticMumbai", "maticmum" ], // @TODO: Future remove these alts
plugins: [
getGasStationPlugin("https:/\/gasstation-testnet.polygon.technology/v2")
]
});
registerEth("optimism", 10, {
ensNetwork: 1,
plugins: [ ]
});
registerEth("optimism-goerli", 420, { });
registerEth("optimism-sepolia", 11155420, { });
registerEth("xdai", 100, { ensNetwork: 1 });
}

8
dev/env/node_modules/ethers/src.ts/providers/pagination.ts generated vendored Executable file
View File

@@ -0,0 +1,8 @@
export interface PaginationResult<R> extends Array<R> {
next(): Promise<PaginationResult<R>>;
// The total number of results available or null if unknown
totalResults: null | number;
done: boolean;
}

View File

@@ -0,0 +1,35 @@
import { AbstractProviderPlugin } from "./abstract-provider.js";
import { defineProperties } from "../utils/index.js";
import type { AbstractProvider, PerformActionRequest } from "./abstract-provider.js";
export const PluginIdFallbackProvider = "org.ethers.plugins.provider.QualifiedPlugin";
export class CheckQualifiedPlugin implements AbstractProviderPlugin {
declare name: string;
constructor() {
defineProperties<CheckQualifiedPlugin>(this, { name: PluginIdFallbackProvider });
}
connect(provider: AbstractProvider): CheckQualifiedPlugin {
return this;
}
// Retruns true if this value should be considered qualified for
// inclusion in the quorum.
isQualified(action: PerformActionRequest, result: any): boolean {
return true;
}
}
export class PossiblyPrunedTransactionPlugin extends CheckQualifiedPlugin {
isQualified(action: PerformActionRequest, result: any): boolean {
if (action.method === "getTransaction" || action.method === "getTransactionReceipt") {
if (result == null) { return false; }
}
return super.isQualified(action, result);
}
}

View File

@@ -0,0 +1,281 @@
import { defineProperties } from "../utils/properties.js";
import { assertArgument } from "../utils/index.js";
import type { FeeData, Provider } from "./provider.js";
import type { FetchRequest } from "../utils/fetch.js";
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);
}
// validate(network: Network): NetworkPlugin {
// return this;
// }
}
/**
* 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) }`);
const props: Record<string, number> = { effectiveBlock };
function set(name: keyof GasCostParameters, nullish: number): void {
let value = (costs || { })[name];
if (value == null) { value = nullish; }
assertArgument(typeof(value) === "number", `invalud value for ${ name }`, "costs", costs);
props[name] = value;
}
set("txBase", 21000);
set("txCreate", 32000);
set("txDataZero", 4);
set("txDataNonzero", 16);
set("txAccessListStorageKey", 1900);
set("txAccessListAddress", 2400);
defineProperties<GasCostPlugin>(this, props);
}
clone(): GasCostPlugin {
return new GasCostPlugin(this.effectiveBlock, this);
}
}
/**
* 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 Registrty Contract address.
*/
readonly address!: string;
/**
* 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, {
address: (address || EnsAddress),
targetNetwork: ((targetNetwork == null) ? 1: targetNetwork)
});
}
clone(): EnsPlugin {
return new EnsPlugin(this.address, this.targetNetwork);
}
}
/**
* 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);
}
clone(): FeeDataNetworkPlugin {
return new FeeDataNetworkPlugin(this.#feeDataFunc);
}
}
export class FetchUrlFeeDataNetworkPlugin extends NetworkPlugin {
readonly #url: string;
readonly #processFunc: (f: () => Promise<FeeData>, p: Provider, r: FetchRequest) => Promise<{ gasPrice?: null | bigint, maxFeePerGas?: null | bigint, maxPriorityFeePerGas?: null | bigint }>;
/**
* The URL to initialize the FetchRequest with in %%processFunc%%.
*/
get url(): string { return this.#url; }
/**
* The callback to use when computing the FeeData.
*/
get processFunc(): (f: () => Promise<FeeData>, p: Provider, r: FetchRequest) => Promise<{ gasPrice?: null | bigint, maxFeePerGas?: null | bigint, maxPriorityFeePerGas?: null | bigint }> { return this.#processFunc; }
/**
* Creates a new **FetchUrlFeeDataNetworkPlugin** which will
* be used when computing the fee data for the network.
*/
constructor(url: string, processFunc: (f: () => Promise<FeeData>, p: Provider, r: FetchRequest) => Promise<{ gasPrice?: null | bigint, maxFeePerGas?: null | bigint, maxPriorityFeePerGas?: null | bigint }>) {
super("org.ethers.plugins.network.FetchUrlFeeDataPlugin");
this.#url = url;
this.#processFunc = processFunc;
}
// We are immutable, so we can serve as our own clone
clone(): FetchUrlFeeDataNetworkPlugin { return this; }
}
/*
export class CustomBlockNetworkPlugin extends NetworkPlugin {
readonly #blockFunc: (provider: Provider, block: BlockParams<string>) => Block<string>;
readonly #blockWithTxsFunc: (provider: Provider, block: BlockParams<TransactionResponseParams>) => Block<TransactionResponse>;
constructor(blockFunc: (provider: Provider, block: BlockParams<string>) => Block<string>, blockWithTxsFunc: (provider: Provider, block: BlockParams<TransactionResponseParams>) => Block<TransactionResponse>) {
super("org.ethers.network-plugins.custom-block");
this.#blockFunc = blockFunc;
this.#blockWithTxsFunc = blockWithTxsFunc;
}
async getBlock(provider: Provider, block: BlockParams<string>): Promise<Block<string>> {
return await this.#blockFunc(provider, block);
}
async getBlockions(provider: Provider, block: BlockParams<TransactionResponseParams>): Promise<Block<TransactionResponse>> {
return await this.#blockWithTxsFunc(provider, block);
}
clone(): CustomBlockNetworkPlugin {
return new CustomBlockNetworkPlugin(this.#blockFunc, this.#blockWithTxsFunc);
}
}
*/

View File

@@ -0,0 +1,166 @@
/**
* [[link-alchemy]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Sepolia Testnet (``sepolia``)
* - Arbitrum (``arbitrum``)
* - Arbitrum Goerli Testnet (``arbitrum-goerli``)
* - Arbitrum Sepolia Testnet (``arbitrum-sepolia``)
* - Base (``base``)
* - Base Goerlia Testnet (``base-goerli``)
* - Base Sepolia Testnet (``base-sepolia``)
* - Optimism (``optimism``)
* - Optimism Goerli Testnet (``optimism-goerli``)
* - Optimism Sepolia Testnet (``optimism-sepolia``)
* - Polygon (``matic``)
* - Polygon Amoy Testnet (``matic-amoy``)
* - Polygon Mumbai Testnet (``matic-mumbai``)
*
* @_subsection: api/providers/thirdparty:Alchemy [providers-alchemy]
*/
import {
defineProperties, resolveProperties, assert, assertArgument,
FetchRequest
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { AbstractProvider, PerformActionRequest } from "./abstract-provider.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
const defaultApiKey = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC"
function getHost(name: string): string {
switch(name) {
case "mainnet":
return "eth-mainnet.g.alchemy.com";
case "goerli":
return "eth-goerli.g.alchemy.com";
case "sepolia":
return "eth-sepolia.g.alchemy.com";
case "arbitrum":
return "arb-mainnet.g.alchemy.com";
case "arbitrum-goerli":
return "arb-goerli.g.alchemy.com";
case "arbitrum-sepolia":
return "arb-sepolia.g.alchemy.com";
case "base":
return "base-mainnet.g.alchemy.com";
case "base-goerli":
return "base-goerli.g.alchemy.com";
case "base-sepolia":
return "base-sepolia.g.alchemy.com";
case "matic":
return "polygon-mainnet.g.alchemy.com";
case "matic-amoy":
return "polygon-amoy.g.alchemy.com";
case "matic-mumbai":
return "polygon-mumbai.g.alchemy.com";
case "optimism":
return "opt-mainnet.g.alchemy.com";
case "optimism-goerli":
return "opt-goerli.g.alchemy.com";
case "optimism-sepolia":
return "opt-sepolia.g.alchemy.com";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **AlchemyProvider** connects to the [[link-alchemy]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-alchemy-signup).
*
* @_docloc: api/providers/thirdparty
*/
export class AlchemyProvider extends JsonRpcProvider implements CommunityResourcable {
readonly apiKey!: string;
constructor(_network?: Networkish, apiKey?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (apiKey == null) { apiKey = defaultApiKey; }
const request = AlchemyProvider.getRequest(network, apiKey);
super(request, network, { staticNetwork: network });
defineProperties<AlchemyProvider>(this, { apiKey });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new AlchemyProvider(chainId, this.apiKey);
} catch (error) { }
return super._getProvider(chainId);
}
async _perform(req: PerformActionRequest): Promise<any> {
// https://docs.alchemy.com/reference/trace-transaction
if (req.method === "getTransactionResult") {
const { trace, tx } = await resolveProperties({
trace: this.send("trace_transaction", [ req.hash ]),
tx: this.getTransaction(req.hash)
});
if (trace == null || tx == null) { return null; }
let data: undefined | string;
let error = false;
try {
data = trace[0].result.output;
error = (trace[0].error === "Reverted");
} catch (error) { }
if (data) {
assert(!error, "an error occurred during transaction executions", "CALL_EXCEPTION", {
action: "getTransactionResult",
data,
reason: null,
transaction: tx,
invocation: null,
revert: null // @TODO
});
return data;
}
assert(false, "could not parse trace result", "BAD_DATA", { value: trace });
}
return await super._perform(req);
}
isCommunityResource(): boolean {
return (this.apiKey === defaultApiKey);
}
static getRequest(network: Network, apiKey?: string): FetchRequest {
if (apiKey == null) { apiKey = defaultApiKey; }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/v2/${ apiKey }`);
request.allowGzip = true;
if (apiKey === defaultApiKey) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("alchemy");
return true;
}
}
return request;
}
}

159
dev/env/node_modules/ethers/src.ts/providers/provider-ankr.ts generated vendored Executable file
View File

@@ -0,0 +1,159 @@
/**
* [[link-ankr]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Sepolia Testnet (``sepolia``)
* - Arbitrum (``arbitrum``)
* - Base (``base``)
* - Base Goerlia Testnet (``base-goerli``)
* - Base Sepolia Testnet (``base-sepolia``)
* - BNB (``bnb``)
* - BNB Testnet (``bnbt``)
* - Filecoin (``filecoin``)
* - Filecoin Calibration Testnet (``filecoin-calibration``)
* - Optimism (``optimism``)
* - Optimism Goerli Testnet (``optimism-goerli``)
* - Optimism Sepolia Testnet (``optimism-sepolia``)
* - Polygon (``matic``)
* - Polygon Mumbai Testnet (``matic-mumbai``)
*
* @_subsection: api/providers/thirdparty:Ankr [providers-ankr]
*/
import {
defineProperties, FetchRequest, assertArgument
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
import type { JsonRpcError, JsonRpcPayload } from "./provider-jsonrpc.js";
const defaultApiKey = "9f7d929b018cdffb338517efa06f58359e86ff1ffd350bc889738523659e7972";
function getHost(name: string): string {
switch (name) {
case "mainnet":
return "rpc.ankr.com/eth";
case "goerli":
return "rpc.ankr.com/eth_goerli";
case "sepolia":
return "rpc.ankr.com/eth_sepolia";
case "arbitrum":
return "rpc.ankr.com/arbitrum";
case "base":
return "rpc.ankr.com/base";
case "base-goerli":
return "rpc.ankr.com/base_goerli";
case "base-sepolia":
return "rpc.ankr.com/base_sepolia";
case "bnb":
return "rpc.ankr.com/bsc";
case "bnbt":
return "rpc.ankr.com/bsc_testnet_chapel";
case "filecoin":
return "rpc.ankr.com/filecoin";
case "filecoin-calibration":
return "rpc.ankr.com/filecoin_testnet";
case "matic":
return "rpc.ankr.com/polygon";
case "matic-mumbai":
return "rpc.ankr.com/polygon_mumbai";
case "optimism":
return "rpc.ankr.com/optimism";
case "optimism-goerli":
return "rpc.ankr.com/optimism_testnet";
case "optimism-sepolia":
return "rpc.ankr.com/optimism_sepolia";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **AnkrProvider** connects to the [[link-ankr]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-ankr-signup).
*/
export class AnkrProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The API key for the Ankr connection.
*/
readonly apiKey!: string;
/**
* Create a new **AnkrProvider**.
*
* By default connecting to ``mainnet`` with a highly throttled
* API key.
*/
constructor(_network?: Networkish, apiKey?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (apiKey == null) { apiKey = defaultApiKey; }
// Ankr does not support filterId, so we force polling
const options = { polling: true, staticNetwork: network };
const request = AnkrProvider.getRequest(network, apiKey);
super(request, network, options);
defineProperties<AnkrProvider>(this, { apiKey });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new AnkrProvider(chainId, this.apiKey);
} catch (error) { }
return super._getProvider(chainId);
}
/**
* Returns a prepared request for connecting to %%network%% with
* %%apiKey%%.
*/
static getRequest(network: Network, apiKey?: null | string): FetchRequest {
if (apiKey == null) { apiKey = defaultApiKey; }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/${ apiKey }`);
request.allowGzip = true;
if (apiKey === defaultApiKey) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("AnkrProvider");
return true;
};
}
return request;
}
getRpcError(payload: JsonRpcPayload, error: JsonRpcError): Error {
if (payload.method === "eth_sendRawTransaction") {
if (error && error.error && error.error.message === "INTERNAL_ERROR: could not replace existing tx") {
error.error.message = "replacement transaction underpriced";
}
}
return super.getRpcError(payload, error);
}
isCommunityResource(): boolean {
return (this.apiKey === defaultApiKey);
}
}

View File

@@ -0,0 +1,167 @@
/**
* [[link-blockscout]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Sepolia Testnet (``sepolia``)
* - Holesky Testnet (``holesky``)
* - Ethereum Classic (``classic``)
* - Arbitrum (``arbitrum``)
* - Base (``base``)
* - Base Sepolia Testnet (``base-sepolia``)
* - Gnosis (``xdai``)
* - Optimism (``optimism``)
* - Optimism Sepolia Testnet (``optimism-sepolia``)
* - Polygon (``matic``)
*
* @_subsection: api/providers/thirdparty:Blockscout [providers-blockscout]
*/
import {
assertArgument, defineProperties, FetchRequest, isHexString
} from "../utils/index.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { AbstractProvider, PerformActionRequest } from "./abstract-provider.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
import type { JsonRpcPayload, JsonRpcError } from "./provider-jsonrpc.js";
function getUrl(name: string): string {
switch(name) {
case "mainnet":
return "https:/\/eth.blockscout.com/api/eth-rpc";
case "sepolia":
return "https:/\/eth-sepolia.blockscout.com/api/eth-rpc";
case "holesky":
return "https:/\/eth-holesky.blockscout.com/api/eth-rpc";
case "classic":
return "https:/\/etc.blockscout.com/api/eth-rpc";
case "arbitrum":
return "https:/\/arbitrum.blockscout.com/api/eth-rpc";
case "base":
return "https:/\/base.blockscout.com/api/eth-rpc";
case "base-sepolia":
return "https:/\/base-sepolia.blockscout.com/api/eth-rpc";
case "matic":
return "https:/\/polygon.blockscout.com/api/eth-rpc";
case "optimism":
return "https:/\/optimism.blockscout.com/api/eth-rpc";
case "optimism-sepolia":
return "https:/\/optimism-sepolia.blockscout.com/api/eth-rpc";
case "xdai":
return "https:/\/gnosis.blockscout.com/api/eth-rpc";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **BlockscoutProvider** connects to the [[link-blockscout]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-blockscout).
*/
export class BlockscoutProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The API key.
*/
readonly apiKey!: null | string;
/**
* Creates a new **BlockscoutProvider**.
*/
constructor(_network?: Networkish, apiKey?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (apiKey == null) { apiKey = null; }
const request = BlockscoutProvider.getRequest(network);
super(request, network, { staticNetwork: network });
defineProperties<BlockscoutProvider>(this, { apiKey });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new BlockscoutProvider(chainId, this.apiKey);
} catch (error) { }
return super._getProvider(chainId);
}
isCommunityResource(): boolean {
return (this.apiKey === null);
}
getRpcRequest(req: PerformActionRequest): null | { method: string, args: Array<any> } {
// Blockscout enforces the TAG argument for estimateGas
const resp = super.getRpcRequest(req);
if (resp && resp.method === "eth_estimateGas" && resp.args.length == 1) {
resp.args = resp.args.slice();
resp.args.push("latest");
}
return resp;
}
getRpcError(payload: JsonRpcPayload, _error: JsonRpcError): Error {
const error = _error ? _error.error: null;
// Blockscout currently drops the VM result and replaces it with a
// human-readable string, so we need to make it machine-readable.
if (error && error.code === -32015 && !isHexString(error.data || "", true)) {
const panicCodes = <Record<string, string>>{
"assert(false)": "01",
"arithmetic underflow or overflow": "11",
"division or modulo by zero": "12",
"out-of-bounds array access; popping on an empty array": "31",
"out-of-bounds access of an array or bytesN": "32"
};
let panicCode = "";
if (error.message === "VM execution error.") {
// eth_call passes this message
panicCode = panicCodes[error.data] || "";
} else if (panicCodes[error.message || ""]) {
panicCode = panicCodes[error.message || ""];
}
if (panicCode) {
error.message += ` (reverted: ${ error.data })`;
error.data = "0x4e487b7100000000000000000000000000000000000000000000000000000000000000" + panicCode;
}
} else if (error && error.code === -32000) {
if (error.message === "wrong transaction nonce") {
error.message += " (nonce too low)";
}
}
return super.getRpcError(payload, _error);
}
/**
* Returns a prepared request for connecting to %%network%%
* with %%apiKey%%.
*/
static getRequest(network: Network): FetchRequest {
const request = new FetchRequest(getUrl(network.name));
request.allowGzip = true;
return request;
}
}

View File

@@ -0,0 +1,334 @@
import { assertArgument, makeError } from "../utils/index.js";
import { JsonRpcApiPollingProvider } from "./provider-jsonrpc.js";
import type {
JsonRpcApiProviderOptions,
JsonRpcError, JsonRpcPayload, JsonRpcResult,
JsonRpcSigner
} from "./provider-jsonrpc.js";
import type { Network, 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> }
} | {
action: "receiveEip1193Result",
result: any
} | {
action: "receiveEip1193Error",
error: Error
};
/**
* Provider info provided by the [[link-eip-6963]] discovery mechanism.
*/
export interface Eip6963ProviderInfo {
uuid: string;
name: string;
icon: string;
rdns: string;
}
interface Eip6963ProviderDetail {
info: Eip6963ProviderInfo;
provider: Eip1193Provider;
}
interface Eip6963Announcement {
type: "eip6963:announceProvider";
detail: Eip6963ProviderDetail
}
export type BrowserProviderOptions = {
polling?: boolean;
staticNetwork?: null | boolean | Network;
cacheTimeout?: number;
pollingInterval?: number;
providerInfo?: Eip6963ProviderInfo;
};
/**
* Specifies how [[link-eip-6963]] discovery should proceed.
*
* See: [[BrowserProvider-discover]]
*/
export interface BrowserDiscoverOptions {
/**
* Override provider detection with this provider.
*/
provider?: Eip1193Provider;
/**
* Duration to wait to detect providers. (default: 300ms)
*/
timeout?: number;
/**
* Return the first detected provider. Otherwise wait for %%timeout%%
* and allowing filtering before selecting the desired provider.
*/
anyProvider?: boolean;
/**
* Use the provided window context. Useful in non-standard
* environments or to hijack where a provider comes from.
*/
window?: any;
/**
* Explicitly choose which provider to used once scanning is complete.
*/
filter?: (found: Array<Eip6963ProviderInfo>) => null | BrowserProvider |
Eip6963ProviderInfo;
}
/**
* 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>;
#providerInfo: null | Eip6963ProviderInfo;
/**
* Connect to the %%ethereum%% provider, optionally forcing the
* %%network%%.
*/
constructor(ethereum: Eip1193Provider, network?: Networkish, _options?: BrowserProviderOptions) {
// Copy the options
const options: JsonRpcApiProviderOptions = Object.assign({ },
((_options != null) ? _options: { }),
{ batchMaxCount: 1 });
assertArgument(ethereum && ethereum.request, "invalid EIP-1193 provider", "ethereum", ethereum);
super(network, options);
this.#providerInfo = null;
if (_options && _options.providerInfo) {
this.#providerInfo = _options.providerInfo;
}
this.#request = async (method: string, params: Array<any> | Record<string, any>) => {
const payload = { method, params };
this.emit("debug", { action: "sendEip1193Request", payload });
try {
const result = await ethereum.request(payload);
this.emit("debug", { action: "receiveEip1193Result", result });
return result;
} catch (e: any) {
const error = new Error(e.message);
(<any>error).code = e.code;
(<any>error).data = e.data;
(<any>error).payload = payload;
this.emit("debug", { action: "receiveEip1193Error", error });
throw error;
}
};
}
get providerInfo(): null | Eip6963ProviderInfo {
return this.#providerInfo;
}
async send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
await this._start();
return await super.send(method, params);
}
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult | JsonRpcError>> {
assertArgument(!Array.isArray(payload), "EIP-1193 does not support batch request", "payload", payload);
try {
const result = await this.#request(payload.method, payload.params || [ ]);
return [ { id: payload.id, result } ];
} catch (e: any) {
return [ {
id: payload.id,
error: { code: e.code, data: e.data, message: e.message }
} ];
}
}
getRpcError(payload: JsonRpcPayload, error: JsonRpcError): Error {
error = JSON.parse(JSON.stringify(error));
// EIP-1193 gives us some machine-readable error codes, so rewrite
// them into Ethers standard errors.
switch (error.error.code || -1) {
case 4001:
error.error.message = `ethers-user-denied: ${ error.error.message }`;
break;
case 4200:
error.error.message = `ethers-unsupported: ${ error.error.message }`;
break;
}
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; }
const accounts = await this.send("eth_accounts", [ ]);
if (typeof(address) === "number") {
return (accounts.length > address);
}
address = address.toLowerCase();
return accounts.filter((a: string) => (a.toLowerCase() === address)).length !== 0;
}
async getSigner(address?: number | string): Promise<JsonRpcSigner> {
if (address == null) { address = 0; }
if (!(await this.hasSigner(address))) {
try {
await this.#request("eth_requestAccounts", [ ]);
} catch (error: any) {
const payload = error.payload;
throw this.getRpcError(payload, { id: payload.id, error });
}
}
return await super.getSigner(address);
}
/**
* Discover and connect to a Provider in the Browser using the
* [[link-eip-6963]] discovery mechanism. If no providers are
* present, ``null`` is resolved.
*/
static async discover(options?: BrowserDiscoverOptions): Promise<null | BrowserProvider> {
if (options == null) { options = { }; }
if (options.provider) {
return new BrowserProvider(options.provider);
}
const context = options.window ? options.window:
(typeof(window) !== "undefined") ? window: null;
if (context == null) { return null; }
const anyProvider = options.anyProvider;
if (anyProvider && context.ethereum) {
return new BrowserProvider(context.ethereum);
}
if (!("addEventListener" in context && "dispatchEvent" in context
&& "removeEventListener" in context)) {
return null;
}
const timeout = options.timeout ? options.timeout: 300;
if (timeout === 0) { return null; }
return await (new Promise((resolve, reject) => {
let found: Array<Eip6963ProviderDetail> = [ ];
const addProvider = (event: Eip6963Announcement) => {
found.push(event.detail);
if (anyProvider) { finalize(); }
};
const finalize = () => {
clearTimeout(timer);
if (found.length) {
// If filtering is provided:
if (options && options.filter) {
// Call filter, with a copies of found provider infos
const filtered = options.filter(found.map(i =>
Object.assign({ }, (i.info))));
if (filtered == null) {
// No provider selected
resolve(null);
} else if (filtered instanceof BrowserProvider) {
// Custom provider created
resolve(filtered);
} else {
// Find the matching provider
let match: null | Eip6963ProviderDetail = null;
if (filtered.uuid) {
const matches = found.filter(f =>
(filtered.uuid === f.info.uuid));
// @TODO: What should happen if multiple values
// for the same UUID?
match = matches[0];
}
if (match) {
const { provider, info } = match;
resolve(new BrowserProvider(provider, undefined, {
providerInfo: info
}));
} else {
reject(makeError("filter returned unknown info", "UNSUPPORTED_OPERATION", {
value: filtered
}));
}
}
} else {
// Pick the first found provider
const { provider, info } = found[0];
resolve(new BrowserProvider(provider, undefined, {
providerInfo: info
}));
}
} else {
// Nothing found
resolve(null);
}
context.removeEventListener(<any>"eip6963:announceProvider",
addProvider);
};
const timer = setTimeout(() => { finalize(); }, timeout);
context.addEventListener(<any>"eip6963:announceProvider",
addProvider);
context.dispatchEvent(new Event("eip6963:requestProvider"));
}));
}
}

View File

@@ -0,0 +1,113 @@
/**
* [[link-chainstack]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Arbitrum (``arbitrum``)
* - BNB Smart Chain Mainnet (``bnb``)
* - Polygon (``matic``)
*
* @_subsection: api/providers/thirdparty:Chainstack [providers-chainstack]
*/
import {
defineProperties, FetchRequest, assertArgument
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { AbstractProvider } from "./abstract-provider.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
function getApiKey(name: string): string {
switch (name) {
case "mainnet": return "39f1d67cedf8b7831010a665328c9197";
case "arbitrum": return "0550c209db33c3abf4cc927e1e18cea1"
case "bnb": return "98b5a77e531614387366f6fc5da097f8";
case "matic": return "cd9d4d70377471aa7c142ec4a4205249";
}
assertArgument(false, "unsupported network", "network", name);
}
function getHost(name: string): string {
switch(name) {
case "mainnet":
return "ethereum-mainnet.core.chainstack.com";
case "arbitrum":
return "arbitrum-mainnet.core.chainstack.com";
case "bnb":
return "bsc-mainnet.core.chainstack.com";
case "matic":
return "polygon-mainnet.core.chainstack.com";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **ChainstackProvider** connects to the [[link-chainstack]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-chainstack).
*/
export class ChainstackProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The API key for the Chainstack connection.
*/
readonly apiKey!: string;
/**
* Creates a new **ChainstackProvider**.
*/
constructor(_network?: Networkish, apiKey?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (apiKey == null) { apiKey = getApiKey(network.name); }
const request = ChainstackProvider.getRequest(network, apiKey);
super(request, network, { staticNetwork: network });
defineProperties<ChainstackProvider>(this, { apiKey });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new ChainstackProvider(chainId, this.apiKey);
} catch (error) { }
return super._getProvider(chainId);
}
isCommunityResource(): boolean {
return (this.apiKey === getApiKey(this._network.name));
}
/**
* Returns a prepared request for connecting to %%network%%
* with %%apiKey%% and %%projectSecret%%.
*/
static getRequest(network: Network, apiKey?: null | string): FetchRequest {
if (apiKey == null) { apiKey = getApiKey(network.name); }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/${ apiKey }`);
request.allowGzip = true;
if (apiKey === getApiKey(network.name)) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("ChainstackProvider");
return true;
};
}
return request;
}
}

View File

@@ -0,0 +1,24 @@
/**
* About Cloudflare
*
* @_subsection: api/providers/thirdparty:Cloudflare [providers-cloudflare]
*/
import { assertArgument } from "../utils/index.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { Networkish } from "./network.js";
/**
* About Cloudflare...
*/
export class CloudflareProvider extends JsonRpcProvider {
constructor(_network?: Networkish) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
assertArgument(network.name === "mainnet", "unsupported network", "network", _network);
super("https:/\/cloudflare-eth.com/", network, { staticNetwork: network });
}
}

View File

@@ -0,0 +1,687 @@
/**
* [[link-etherscan]] provides a third-party service for connecting to
* various blockchains over a combination of JSON-RPC and custom API
* endpoints.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Sepolia Testnet (``sepolia``)
* - Holesky Testnet (``holesky``)
* - Arbitrum (``arbitrum``)
* - Arbitrum Goerli Testnet (``arbitrum-goerli``)
* - Base (``base``)
* - Base Sepolia Testnet (``base-sepolia``)
* - BNB Smart Chain Mainnet (``bnb``)
* - BNB Smart Chain Testnet (``bnbt``)
* - Optimism (``optimism``)
* - Optimism Goerli Testnet (``optimism-goerli``)
* - Polygon (``matic``)
* - Polygon Mumbai Testnet (``matic-mumbai``)
* - Polygon Amoy Testnet (``matic-amoy``)
*
* @_subsection api/providers/thirdparty:Etherscan [providers-etherscan]
*/
import { AbiCoder } from "../abi/index.js";
import { Contract } from "../contract/index.js";
import { accessListify, Transaction } from "../transaction/index.js";
import {
defineProperties,
hexlify, toQuantity,
FetchRequest,
assert, assertArgument, isError,
// parseUnits,
toUtf8String
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js";
import { Network } from "./network.js";
import { NetworkPlugin } from "./plugins-network.js";
import { showThrottleMessage } from "./community.js";
import { PerformActionRequest } from "./abstract-provider.js";
import type { Networkish } from "./network.js";
//import type { } from "./pagination";
import type { TransactionRequest } from "./provider.js";
// See: https://docs.etherscan.io/supported-chains
const Supported = (
"1 11155111 17000 560048 2741 11124 33111 33139 42170 " +
"42161 421614 43114 43113 8453 84532 80069 80094 199 1029 81457 " +
"168587773 56 97 42220 11142220 252 2523 100 999 737373 747474 " +
"59144 59141 5000 5003 43521 143 10143 1287 1284 1285 10 " +
"11155420 204 5611 80002 137 534352 534351 1329 1328 146 14601 " +
"988 2201 1923 1924 167013 167000 130 1301 480 4801 51 50 324 300"
).split(/ /g);
const THROTTLE = 2000;
function isPromise<T = any>(value: any): value is Promise<T> {
return (value && typeof(value.then) === "function");
}
/**
* When subscribing to the ``"debug"`` event on an Etherscan-based
* provider, the events receive a **DebugEventEtherscanProvider**
* payload.
*
* @_docloc: api/providers/thirdparty:Etherscan
*/
export type DebugEventEtherscanProvider = {
action: "sendRequest",
id: number,
url: string,
payload: Record<string, any>
} | {
action: "receiveRequest",
id: number,
result: any
} | {
action: "receiveError",
id: number,
error: any
};
const EtherscanPluginId = "org.ethers.plugins.provider.Etherscan";
/**
* A Network can include an **EtherscanPlugin** to provide
* a custom base URL.
*
* @_docloc: api/providers/thirdparty:Etherscan
*/
export class EtherscanPlugin extends NetworkPlugin {
/**
* The Etherscan API base URL.
*/
readonly baseUrl!: string;
/**
* Creates a new **EtherscanProvider** which will use
* %%baseUrl%%.
*/
constructor(baseUrl: string) {
super(EtherscanPluginId);
defineProperties<EtherscanPlugin>(this, { baseUrl });
}
clone(): EtherscanPlugin {
return new EtherscanPlugin(this.baseUrl);
}
}
const skipKeys = [ "enableCcipRead" ];
let nextId = 1;
/**
* The **EtherscanBaseProvider** is the super-class of
* [[EtherscanProvider]], which should generally be used instead.
*
* Since the **EtherscanProvider** includes additional code for
* [[Contract]] access, in //rare cases// that contracts are not
* used, this class can reduce code size.
*
* @_docloc: api/providers/thirdparty:Etherscan
*/
export class EtherscanProvider extends AbstractProvider {
/**
* The connected network.
*/
readonly network!: Network;
/**
* The API key or null if using the community provided bandwidth.
*/
readonly apiKey!: null | string;
readonly #plugin: null | EtherscanPlugin;
/**
* Creates a new **EtherscanBaseProvider**.
*/
constructor(_network?: Networkish, _apiKey?: string) {
const apiKey = (_apiKey != null) ? _apiKey: null;
super();
const network = Network.from(_network);
assertArgument(Supported.indexOf(`${ network.chainId }`) >= 0,
"unsupported network", "network", network);
this.#plugin = network.getPlugin<EtherscanPlugin>(EtherscanPluginId);
defineProperties<EtherscanProvider>(this, { apiKey, network });
}
/**
* Returns the base URL.
*
* If an [[EtherscanPlugin]] is configured on the
* [[EtherscanBaseProvider_network]], returns the plugin's
* baseUrl.
*
* Deprecated; for Etherscan v2 the base is no longer a simply
* host, but instead a URL including a chainId parameter. Changing
* this to return a URL prefix could break some libraries, so it
* is left intact but will be removed in the future as it is unused.
*/
getBaseUrl(): string {
if (this.#plugin) { return this.#plugin.baseUrl; }
switch(this.network.name) {
case "mainnet":
return "https:/\/api.etherscan.io";
case "goerli":
return "https:/\/api-goerli.etherscan.io";
case "sepolia":
return "https:/\/api-sepolia.etherscan.io";
case "holesky":
return "https:/\/api-holesky.etherscan.io";
case "arbitrum":
return "https:/\/api.arbiscan.io";
case "arbitrum-goerli":
return "https:/\/api-goerli.arbiscan.io";
case "base":
return "https:/\/api.basescan.org";
case "base-sepolia":
return "https:/\/api-sepolia.basescan.org";
case "bnb":
return "https:/\/api.bscscan.com";
case "bnbt":
return "https:/\/api-testnet.bscscan.com";
case "matic":
return "https:/\/api.polygonscan.com";
case "matic-amoy":
return "https:/\/api-amoy.polygonscan.com";
case "matic-mumbai":
return "https:/\/api-testnet.polygonscan.com";
case "optimism":
return "https:/\/api-optimistic.etherscan.io";
case "optimism-goerli":
return "https:/\/api-goerli-optimistic.etherscan.io";
default:
}
assertArgument(false, "unsupported network", "network", this.network);
}
/**
* Returns the URL for the %%module%% and %%params%%.
*/
getUrl(module: string, params: Record<string, string>): string {
let query = Object.keys(params).reduce((accum, key) => {
const value = params[key];
if (value != null) {
accum += `&${ key }=${ value }`
}
return accum
}, "");
if (this.apiKey) { query += `&apikey=${ this.apiKey }`; }
return `https:/\/api.etherscan.io/v2/api?chainid=${ this.network.chainId }&module=${ module }${ query }`;
}
/**
* Returns the URL for using POST requests.
*/
getPostUrl(): string {
return `https:/\/api.etherscan.io/v2/api?chainid=${ this.network.chainId }`;
}
/**
* Returns the parameters for using POST requests.
*/
getPostData(module: string, params: Record<string, any>): Record<string, any> {
params.module = module;
params.apikey = this.apiKey;
params.chainid = this.network.chainId;
return params;
}
async detectNetwork(): Promise<Network> {
return this.network;
}
/**
* Resolves to the result of calling %%module%% with %%params%%.
*
* If %%post%%, the request is made as a POST request.
*/
async fetch(module: string, params: Record<string, any>, post?: boolean): Promise<any> {
const id = nextId++;
const url = (post ? this.getPostUrl(): this.getUrl(module, params));
const payload = (post ? this.getPostData(module, params): null);
this.emit("debug", { action: "sendRequest", id, url, payload: payload });
const request = new FetchRequest(url);
request.setThrottleParams({ slotInterval: 1000 });
request.retryFunc = (req, resp, attempt: number) => {
if (this.isCommunityResource()) {
showThrottleMessage("Etherscan");
}
return Promise.resolve(true);
};
request.processFunc = async (request, response) => {
const result = response.hasBody() ? JSON.parse(toUtf8String(response.body)): { };
const throttle = ((typeof(result.result) === "string") ? result.result: "").toLowerCase().indexOf("rate limit") >= 0;
if (module === "proxy") {
// This JSON response indicates we are being throttled
if (result && result.status == 0 && result.message == "NOTOK" && throttle) {
this.emit("debug", { action: "receiveError", id, reason: "proxy-NOTOK", error: result });
response.throwThrottleError(result.result, THROTTLE);
}
} else {
if (throttle) {
this.emit("debug", { action: "receiveError", id, reason: "null result", error: result.result });
response.throwThrottleError(result.result, THROTTLE);
}
}
return response;
};
if (payload) {
request.setHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
request.body = Object.keys(payload).map((k) => `${ k }=${ payload[k] }`).join("&");
}
const response = await request.send();
try {
response.assertOk();
} catch (error) {
this.emit("debug", { action: "receiveError", id, error, reason: "assertOk" });
assert(false, "response error", "SERVER_ERROR", { request, response });
}
if (!response.hasBody()) {
this.emit("debug", { action: "receiveError", id, error: "missing body", reason: "null body" });
assert(false, "missing response", "SERVER_ERROR", { request, response });
}
const result = JSON.parse(toUtf8String(response.body));
if (module === "proxy") {
if (result.jsonrpc != "2.0") {
this.emit("debug", { action: "receiveError", id, result, reason: "invalid JSON-RPC" });
assert(false, "invalid JSON-RPC response (missing jsonrpc='2.0')", "SERVER_ERROR", { request, response, info: { result } });
}
if (result.error) {
this.emit("debug", { action: "receiveError", id, result, reason: "JSON-RPC error" });
assert(false, "error response", "SERVER_ERROR", { request, response, info: { result } });
}
this.emit("debug", { action: "receiveRequest", id, result });
return result.result;
} else {
// getLogs, getHistory have weird success responses
if (result.status == 0 && (result.message === "No records found" || result.message === "No transactions found")) {
this.emit("debug", { action: "receiveRequest", id, result });
return result.result;
}
if (result.status != 1 || (typeof(result.message) === "string" && !result.message.match(/^OK/))) {
this.emit("debug", { action: "receiveError", id, result });
assert(false, "error response", "SERVER_ERROR", { request, response, info: { result } });
}
this.emit("debug", { action: "receiveRequest", id, result });
return result.result;
}
}
/**
* Returns %%transaction%% normalized for the Etherscan API.
*/
_getTransactionPostData(transaction: TransactionRequest): Record<string, string> {
const result: Record<string, string> = { };
for (let key in transaction) {
if (skipKeys.indexOf(key) >= 0) { continue; }
if ((<any>transaction)[key] == null) { continue; }
let value = (<any>transaction)[key];
if (key === "type" && value === 0) { continue; }
if (key === "blockTag" && value === "latest") { continue; }
// Quantity-types require no leading zero, unless 0
if ((<any>{ type: true, gasLimit: true, gasPrice: true, maxFeePerGs: true, maxPriorityFeePerGas: true, nonce: true, value: true })[key]) {
value = toQuantity(value);
} else if (key === "accessList") {
value = "[" + accessListify(value).map((set) => {
return `{address:"${ set.address }",storageKeys:["${ set.storageKeys.join('","') }"]}`;
}).join(",") + "]";
} else if (key === "blobVersionedHashes") {
if (value.length === 0) { continue; }
// @TODO: update this once the API supports blobs
assert(false, "Etherscan API does not support blobVersionedHashes", "UNSUPPORTED_OPERATION", {
operation: "_getTransactionPostData",
info: { transaction }
});
} else {
value = hexlify(value);
}
result[key] = value;
}
return result;
}
/**
* Throws the normalized Etherscan error.
*/
_checkError(req: PerformActionRequest, error: Error, transaction: any): never {
// Pull any message out if, possible
let message = "";
if (isError(error, "SERVER_ERROR")) {
// Check for an error emitted by a proxy call
try {
message = (<any>error).info.result.error.message;
} catch (e) { }
if (!message) {
try {
message = (<any>error).info.message;
} catch (e) { }
}
}
if (req.method === "estimateGas") {
if (!message.match(/revert/i) && message.match(/insufficient funds/i)) {
assert(false, "insufficient funds", "INSUFFICIENT_FUNDS", {
transaction: req.transaction
});
}
}
if (req.method === "call" || req.method === "estimateGas") {
if (message.match(/execution reverted/i)) {
let data = "";
try {
data = (<any>error).info.result.error.data;
} catch (error) { }
const e = AbiCoder.getBuiltinCallException(req.method, <any>req.transaction, data);
e.info = { request: req, error }
throw e;
}
}
if (message) {
if (req.method === "broadcastTransaction") {
const transaction = Transaction.from(req.signedTransaction);
if (message.match(/replacement/i) && message.match(/underpriced/i)) {
assert(false, "replacement fee too low", "REPLACEMENT_UNDERPRICED", {
transaction
});
}
if (message.match(/insufficient funds/)) {
assert(false, "insufficient funds for intrinsic transaction cost", "INSUFFICIENT_FUNDS", {
transaction
});
}
if (message.match(/same hash was already imported|transaction nonce is too low|nonce too low/)) {
assert(false, "nonce has already been used", "NONCE_EXPIRED", {
transaction
});
}
}
}
// Something we could not process
throw error;
}
async _detectNetwork(): Promise<Network> {
return this.network;
}
async _perform(req: PerformActionRequest): Promise<any> {
switch (req.method) {
case "chainId":
return this.network.chainId;
case "getBlockNumber":
return this.fetch("proxy", { action: "eth_blockNumber" });
case "getGasPrice":
return this.fetch("proxy", { action: "eth_gasPrice" });
case "getPriorityFee":
// This is temporary until Etherscan completes support
if (this.network.name === "mainnet") {
return "1000000000";
} else if (this.network.name === "optimism") {
return "1000000";
} else {
throw new Error("fallback onto the AbstractProvider default");
}
/* Working with Etherscan to get this added:
try {
const test = await this.fetch("proxy", {
action: "eth_maxPriorityFeePerGas"
});
console.log(test);
return test;
} catch (e) {
console.log("DEBUG", e);
throw e;
}
*/
/* This might be safe; but due to rounding neither myself
or Etherscan are necessarily comfortable with this. :)
try {
const result = await this.fetch("gastracker", { action: "gasoracle" });
console.log(result);
const gasPrice = parseUnits(result.SafeGasPrice, "gwei");
const baseFee = parseUnits(result.suggestBaseFee, "gwei");
const priorityFee = gasPrice - baseFee;
if (priorityFee < 0) { throw new Error("negative priority fee; defer to abstract provider default"); }
return priorityFee;
} catch (error) {
console.log("DEBUG", error);
throw error;
}
*/
case "getBalance":
// Returns base-10 result
return this.fetch("account", {
action: "balance",
address: req.address,
tag: req.blockTag
});
case "getTransactionCount":
return this.fetch("proxy", {
action: "eth_getTransactionCount",
address: req.address,
tag: req.blockTag
});
case "getCode":
return this.fetch("proxy", {
action: "eth_getCode",
address: req.address,
tag: req.blockTag
});
case "getStorage":
return this.fetch("proxy", {
action: "eth_getStorageAt",
address: req.address,
position: req.position,
tag: req.blockTag
});
case "broadcastTransaction":
return this.fetch("proxy", {
action: "eth_sendRawTransaction",
hex: req.signedTransaction
}, true).catch((error) => {
return this._checkError(req, <Error>error, req.signedTransaction);
});
case "getBlock":
if ("blockTag" in req) {
return this.fetch("proxy", {
action: "eth_getBlockByNumber",
tag: req.blockTag,
boolean: (req.includeTransactions ? "true": "false")
});
}
assert(false, "getBlock by blockHash not supported by Etherscan", "UNSUPPORTED_OPERATION", {
operation: "getBlock(blockHash)"
});
case "getTransaction":
return this.fetch("proxy", {
action: "eth_getTransactionByHash",
txhash: req.hash
});
case "getTransactionReceipt":
return this.fetch("proxy", {
action: "eth_getTransactionReceipt",
txhash: req.hash
});
case "call": {
if (req.blockTag !== "latest") {
throw new Error("EtherscanProvider does not support blockTag for call");
}
const postData = this._getTransactionPostData(req.transaction);
postData.module = "proxy";
postData.action = "eth_call";
try {
return await this.fetch("proxy", postData, true);
} catch (error) {
return this._checkError(req, <Error>error, req.transaction);
}
}
case "estimateGas": {
const postData = this._getTransactionPostData(req.transaction);
postData.module = "proxy";
postData.action = "eth_estimateGas";
try {
return await this.fetch("proxy", postData, true);
} catch (error) {
return this._checkError(req, <Error>error, req.transaction);
}
}
/*
case "getLogs": {
// Needs to complain if more than one address is passed in
const args: Record<string, any> = { action: "getLogs" }
if (params.filter.fromBlock) {
args.fromBlock = checkLogTag(params.filter.fromBlock);
}
if (params.filter.toBlock) {
args.toBlock = checkLogTag(params.filter.toBlock);
}
if (params.filter.address) {
args.address = params.filter.address;
}
// @TODO: We can handle slightly more complicated logs using the logs API
if (params.filter.topics && params.filter.topics.length > 0) {
if (params.filter.topics.length > 1) {
logger.throwError("unsupported topic count", Logger.Errors.UNSUPPORTED_OPERATION, { topics: params.filter.topics });
}
if (params.filter.topics.length === 1) {
const topic0 = params.filter.topics[0];
if (typeof(topic0) !== "string" || topic0.length !== 66) {
logger.throwError("unsupported topic format", Logger.Errors.UNSUPPORTED_OPERATION, { topic0: topic0 });
}
args.topic0 = topic0;
}
}
const logs: Array<any> = await this.fetch("logs", args);
// Cache txHash => blockHash
let blocks: { [tag: string]: string } = {};
// Add any missing blockHash to the logs
for (let i = 0; i < logs.length; i++) {
const log = logs[i];
if (log.blockHash != null) { continue; }
if (blocks[log.blockNumber] == null) {
const block = await this.getBlock(log.blockNumber);
if (block) {
blocks[log.blockNumber] = block.hash;
}
}
log.blockHash = blocks[log.blockNumber];
}
return logs;
}
*/
default:
break;
}
return super._perform(req);
}
async getNetwork(): Promise<Network> {
return this.network;
}
/**
* Resolves to the current price of ether.
*
* This returns ``0`` on any network other than ``mainnet``.
*/
async getEtherPrice(): Promise<number> {
if (this.network.name !== "mainnet") { return 0.0; }
return parseFloat((await this.fetch("stats", { action: "ethprice" })).ethusd);
}
/**
* Resolves to a [Contract]] for %%address%%, using the
* Etherscan API to retreive the Contract ABI.
*/
async getContract(_address: string): Promise<null | Contract> {
let address = this._getAddress(_address);
if (isPromise(address)) { address = await address; }
try {
const resp = await this.fetch("contract", {
action: "getabi", address });
const abi = JSON.parse(resp);
return new Contract(address, abi, this);
} catch (error) {
return null;
}
}
isCommunityResource(): boolean {
return (this.apiKey == null);
}
}

View File

@@ -0,0 +1,801 @@
/**
* A **FallbackProvider** provides resilience, security and performance
* in a way that is customizable and configurable.
*
* @_section: api/providers/fallback-provider:Fallback Provider [about-fallback-provider]
*/
import {
assert, assertArgument, getBigInt, getNumber, isError
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js";
import { Network } from "./network.js"
import type { PerformActionRequest } from "./abstract-provider.js";
import type { Networkish } from "./network.js"
const BN_1 = BigInt("1");
const BN_2 = BigInt("2");
function shuffle<T = any>(array: Array<T>): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
function stall(duration: number): Promise<void> {
return new Promise((resolve) => { setTimeout(resolve, duration); });
}
function getTime(): number { return (new Date()).getTime(); }
function stringify(value: any): string {
return JSON.stringify(value, (key, value) => {
if (typeof(value) === "bigint") {
return { type: "bigint", value: value.toString() };
}
return value;
});
}
/**
* A configuration entry for how to use a [[Provider]].
*/
export interface FallbackProviderConfig {
/**
* The provider.
*/
provider: AbstractProvider;
/**
* 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;
/**
* The priority. Lower priority providers are dispatched first.
*/
priority?: number;
/**
* The amount of weight a provider is given against the quorum.
*/
weight?: number;
};
const defaultConfig = { stallTimeout: 400, priority: 1, weight: 1 };
// We track a bunch of extra stuff that might help debug problems or
// optimize infrastructure later on.
/**
* The statistics and state maintained for a [[Provider]].
*/
export interface FallbackProviderState extends Required<FallbackProviderConfig> {
/**
* The most recent blockNumber this provider has reported (-2 if none).
*/
blockNumber: number;
/**
* The number of total requests ever sent to this provider.
*/
requests: number;
/**
* The number of responses that errored.
*/
errorResponses: number;
/**
* The number of responses that occured after the result resolved.
*/
lateResponses: number;
/**
* How many times syncing was required to catch up the expected block.
*/
outOfSync: number;
/**
* The number of requests which reported unsupported operation.
*/
unsupportedEvents: number;
/**
* A rolling average (5% current duration) for response time.
*/
rollingDuration: number;
/**
* The ratio of quorum-agreed results to total.
*/
score: number;
}
interface Config extends FallbackProviderState {
_updateNumber: null | Promise<any>;
_network: null | Network;
_totalTime: number;
_lastFatalError: null | Error;
_lastFatalErrorTimestamp: number;
}
const defaultState = {
blockNumber: -2, requests: 0, lateResponses: 0, errorResponses: 0,
outOfSync: -1, unsupportedEvents: 0, rollingDuration: 0, score: 0,
_network: null, _updateNumber: null, _totalTime: 0,
_lastFatalError: null, _lastFatalErrorTimestamp: 0
};
async function waitForSync(config: Config, blockNumber: number): Promise<void> {
while (config.blockNumber < 0 || config.blockNumber < blockNumber) {
if (!config._updateNumber) {
config._updateNumber = (async () => {
try {
const blockNumber = await config.provider.getBlockNumber();
if (blockNumber > config.blockNumber) {
config.blockNumber = blockNumber;
}
} catch (error: any) {
config.blockNumber = -2;
config._lastFatalError = error;
config._lastFatalErrorTimestamp = getTime();
}
config._updateNumber = null;
})();
}
await config._updateNumber;
config.outOfSync++;
if (config._lastFatalError) { break; }
}
}
/**
* Additional options to configure a [[FallbackProvider]].
*/
export type FallbackProviderOptions = {
// How many providers must agree on a value before reporting
// back the response
quorum?: number;
// How many providers must have reported the same event
// for it to be emitted (currently unimplmented)
eventQuorum?: number;
// How many providers to dispatch each event to simultaneously.
// Set this to 0 to use getLog polling, which implies eventQuorum
// is equal to quorum. (currently unimplemented)
eventWorkers?: number;
cacheTimeout?: number;
pollingInterval?: number;
};
type RunnerResult = { result: any } | { error: Error };
type RunnerState = {
config: Config;
staller: null | Promise<void>;
didBump: boolean;
perform: null | Promise<any>;
result: null | RunnerResult;
}
function _normalize(value: any): string {
if (value == null) { return "null"; }
if (Array.isArray(value)) {
return "[" + (value.map(_normalize)).join(",") + "]";
}
if (typeof(value) === "object" && typeof(value.toJSON) === "function") {
return _normalize(value.toJSON());
}
switch (typeof(value)) {
case "boolean": case "symbol":
return value.toString();
case "bigint": case "number":
return BigInt(value).toString();
case "string":
return JSON.stringify(value);
case "object": {
const keys = Object.keys(value);
keys.sort();
return "{" + keys.map((k) => `${ JSON.stringify(k) }:${ _normalize(value[k]) }`).join(",") + "}";
}
}
console.log("Could not serialize", value);
throw new Error("Hmm...");
}
function normalizeResult(method: string, value: RunnerResult): { tag: string, value: any } {
if ("error" in value) {
const error = value.error;
let tag: string;
if (isError(error, "CALL_EXCEPTION")) {
tag = _normalize(Object.assign({ }, error, {
shortMessage: undefined, reason: undefined, info: undefined
}));
} else {
tag = _normalize(error)
}
return { tag, value: error };
}
const result = value.result;
return { tag: _normalize(result), value: result };
}
type TallyResult = {
tag: string;
value: any;
weight: number;
};
// This strategy picks the highest weight result, as long as the weight is
// equal to or greater than quorum
function checkQuorum(quorum: number, results: Array<TallyResult>): any | Error {
const tally: Map<string, { value: any, weight: number }> = new Map();
for (const { value, tag, weight } of results) {
const t = tally.get(tag) || { value, weight: 0 };
t.weight += weight;
tally.set(tag, t);
}
let best: null | { value: any, weight: number } = null;
for (const r of tally.values()) {
if (r.weight >= quorum && (!best || r.weight > best.weight)) {
best = r;
}
}
if (best) { return best.value; }
return undefined;
}
function getMedian(quorum: number, results: Array<TallyResult>): undefined | bigint | Error {
let resultWeight = 0;
const errorMap: Map<string, { weight: number, value: Error }> = new Map();
let bestError: null | { weight: number, value: Error } = null;
const values: Array<bigint> = [ ];
for (const { value, tag, weight } of results) {
if (value instanceof Error) {
const e = errorMap.get(tag) || { value, weight: 0 };
e.weight += weight;
errorMap.set(tag, e);
if (bestError == null || e.weight > bestError.weight) { bestError = e; }
} else {
values.push(BigInt(value));
resultWeight += weight;
}
}
if (resultWeight < quorum) {
// We have quorum for an error
if (bestError && bestError.weight >= quorum) { return bestError.value; }
// We do not have quorum for a result
return undefined;
}
// Get the sorted values
values.sort((a, b) => ((a < b) ? -1: (b > a) ? 1: 0));
const mid = Math.floor(values.length / 2);
// Odd-length; take the middle value
if (values.length % 2) { return values[mid]; }
// Even length; take the ceiling of the mean of the center two values
return (values[mid - 1] + values[mid] + BN_1) / BN_2;
}
function getAnyResult(quorum: number, results: Array<TallyResult>): undefined | any | Error {
// If any value or error meets quorum, that is our preferred result
const result = checkQuorum(quorum, results);
if (result !== undefined) { return result; }
// Otherwise, do we have any result?
for (const r of results) {
if (r.value) { return r.value; }
}
// Nope!
return undefined;
}
function getFuzzyMode(quorum: number, results: Array<TallyResult>): undefined | number {
if (quorum === 1) { return getNumber(<bigint>getMedian(quorum, results), "%internal"); }
const tally: Map<number, { result: number, weight: number }> = new Map();
const add = (result: number, weight: number) => {
const t = tally.get(result) || { result, weight: 0 };
t.weight += weight;
tally.set(result, t);
};
for (const { weight, value } of results) {
const r = getNumber(value);
add(r - 1, weight);
add(r, weight);
add(r + 1, weight);
}
let bestWeight = 0;
let bestResult: undefined | number = undefined;
for (const { weight, result } of tally.values()) {
// Use this result, if this result meets quorum and has either:
// - a better weight
// - or equal weight, but the result is larger
if (weight >= quorum && (weight > bestWeight || (bestResult != null && weight === bestWeight && result > bestResult))) {
bestWeight = weight;
bestResult = result;
}
}
return bestResult;
}
/**
* A **FallbackProvider** manages several [[Providers]] providing
* resilience 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>;
#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, options?: FallbackProviderOptions) {
super(network, options);
this.#configs = providers.map((p) => {
if (p instanceof AbstractProvider) {
return Object.assign({ provider: p }, defaultConfig, defaultState );
} else {
return Object.assign({ }, defaultConfig, p, defaultState );
}
});
this.#height = -2;
this.#initialSyncPromise = null;
if (options && options.quorum != null) {
this.quorum = options.quorum;
} else {
this.quorum = Math.ceil(this.#configs.reduce((accum, config) => {
accum += config.weight;
return accum;
}, 0) / 2);
}
this.eventQuorum = 1;
this.eventWorkers = 1;
assertArgument(this.quorum <= this.#configs.reduce((a, c) => (a + c.weight), 0),
"quorum exceed provider weight", "quorum", this.quorum);
}
get providerConfigs(): Array<FallbackProviderState> {
return this.#configs.map((c) => {
const result: any = Object.assign({ }, c);
for (const key in result) {
if (key[0] === "_") { delete result[key]; }
}
return result;
});
}
async _detectNetwork(): Promise<Network> {
return Network.from(getBigInt(await this._perform({ method: "chainId" })));
}
// @TODO: Add support to select providers to be the event subscriber
//_getSubscriber(sub: Subscription): Subscriber {
// 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":
return await provider.broadcastTransaction(req.signedTransaction);
case "call":
return await provider.call(Object.assign({ }, req.transaction, { blockTag: req.blockTag }));
case "chainId":
return (await provider.getNetwork()).chainId;
case "estimateGas":
return await provider.estimateGas(req.transaction);
case "getBalance":
return await provider.getBalance(req.address, req.blockTag);
case "getBlock": {
const block = ("blockHash" in req) ? req.blockHash: req.blockTag;
return await provider.getBlock(block, req.includeTransactions);
}
case "getBlockNumber":
return await provider.getBlockNumber();
case "getCode":
return await provider.getCode(req.address, req.blockTag);
case "getGasPrice":
return (await provider.getFeeData()).gasPrice;
case "getPriorityFee":
return (await provider.getFeeData()).maxPriorityFeePerGas;
case "getLogs":
return await provider.getLogs(req.filter);
case "getStorage":
return await provider.getStorage(req.address, req.position, req.blockTag);
case "getTransaction":
return await provider.getTransaction(req.hash);
case "getTransactionCount":
return await provider.getTransactionCount(req.address, req.blockTag);
case "getTransactionReceipt":
return await provider.getTransactionReceipt(req.hash);
case "getTransactionResult":
return await provider.getTransactionResult(req.hash);
}
}
// Grab the next (random) config that is not already part of
// the running set
#getNextConfig(running: Set<RunnerState>): null | Config {
// @TODO: Maybe do a check here to favour (heavily) providers that
// do not require waitForSync and disfavour providers that
// seem down-ish or are behaving slowly
const configs = Array.from(running).map((r) => r.config)
// Shuffle the states, sorted by priority
const allConfigs = this.#configs.slice();
shuffle(allConfigs);
allConfigs.sort((a, b) => (a.priority - b.priority));
for (const config of allConfigs) {
if (config._lastFatalError) { continue; }
if (configs.indexOf(config) === -1) { return config; }
}
return null;
}
// Adds a new runner (if available) to running.
#addRunner(running: Set<RunnerState>, req: PerformActionRequest): null | RunnerState {
const config = this.#getNextConfig(running);
// No runners available
if (config == null) { return null; }
// Create a new runner
const runner: RunnerState = {
config, result: null, didBump: false,
perform: null, staller: null
};
const now = getTime();
// Start performing this operation
runner.perform = (async () => {
try {
config.requests++;
const result = await this._translatePerform(config.provider, req);
runner.result = { result };
} catch (error: any) {
config.errorResponses++;
runner.result = { error };
}
const dt = (getTime() - now);
config._totalTime += dt;
config.rollingDuration = 0.95 * config.rollingDuration + 0.05 * dt;
runner.perform = null;
})();
// Start a staller; when this times out, it's time to force
// kicking off another runner because we are taking too long
runner.staller = (async () => {
await stall(config.stallTimeout);
runner.staller = null;
})();
running.add(runner);
return runner;
}
// Initializes the blockNumber and network for each runner and
// blocks until initialized
async #initialSync(): Promise<void> {
let initialSync = this.#initialSyncPromise;
if (!initialSync) {
const promises: Array<Promise<any>> = [ ];
this.#configs.forEach((config) => {
promises.push((async () => {
await waitForSync(config, 0);
if (!config._lastFatalError) {
config._network = await config.provider.getNetwork();
}
})());
});
this.#initialSyncPromise = initialSync = (async () => {
// Wait for all providers to have a block number and network
await Promise.all(promises);
// Check all the networks match
let chainId: null | bigint = null;
for (const config of this.#configs) {
if (config._lastFatalError) { continue; }
const network = <Network>(config._network);
if (chainId == null) {
chainId = network.chainId;
} else if (network.chainId !== chainId) {
assert(false, "cannot mix providers on different networks", "UNSUPPORTED_OPERATION", {
operation: "new FallbackProvider"
});
}
}
})();
}
await initialSync
}
async #checkQuorum(running: Set<RunnerState>, req: PerformActionRequest): Promise<any> {
// Get all the result objects
const results: Array<TallyResult> = [ ];
for (const runner of running) {
if (runner.result != null) {
const { tag, value } = normalizeResult(req.method, runner.result);
results.push({ tag, value, weight: runner.config.weight });
}
}
// Are there enough results to event meet quorum?
if (results.reduce((a, r) => (a + r.weight), 0) < this.quorum) {
return undefined;
}
switch (req.method) {
case "getBlockNumber": {
// We need to get the bootstrap block height
if (this.#height === -2) {
this.#height = Math.ceil(getNumber(<bigint>getMedian(this.quorum, this.#configs.filter((c) => (!c._lastFatalError)).map((c) => ({
value: c.blockNumber,
tag: getNumber(c.blockNumber).toString(),
weight: c.weight
})))));
}
// Find the mode across all the providers, allowing for
// a little drift between block heights
const mode = getFuzzyMode(this.quorum, results);
if (mode === undefined) { return undefined; }
if (mode > this.#height) { this.#height = mode; }
return this.#height;
}
case "getGasPrice":
case "getPriorityFee":
case "estimateGas":
return getMedian(this.quorum, results);
case "getBlock":
// Pending blocks are in the mempool and already
// quite untrustworthy; just grab anything
if ("blockTag" in req && req.blockTag === "pending") {
return getAnyResult(this.quorum, results);
}
return checkQuorum(this.quorum, results);
case "call":
case "chainId":
case "getBalance":
case "getTransactionCount":
case "getCode":
case "getStorage":
case "getTransaction":
case "getTransactionReceipt":
case "getLogs":
return checkQuorum(this.quorum, results);
case "broadcastTransaction":
return getAnyResult(this.quorum, results);
}
assert(false, "unsupported method", "UNSUPPORTED_OPERATION", {
operation: `_perform(${ stringify((<any>req).method) })`
});
}
async #waitForQuorum(running: Set<RunnerState>, req: PerformActionRequest): Promise<any> {
if (running.size === 0) { throw new Error("no runners?!"); }
// Any promises that are interesting to watch for; an expired stall
// or a successful perform
const interesting: Array<Promise<void>> = [ ];
let newRunners = 0;
for (const runner of running) {
// No responses, yet; keep an eye on it
if (runner.perform) {
interesting.push(runner.perform);
}
// Still stalling...
if (runner.staller) {
interesting.push(runner.staller);
continue;
}
// This runner has already triggered another runner
if (runner.didBump) { continue; }
// Got a response (result or error) or stalled; kick off another runner
runner.didBump = true;
newRunners++;
}
// Check if we have reached quorum on a result (or error)
const value = await this.#checkQuorum(running, req);
if (value !== undefined) {
if (value instanceof Error) { throw value; }
return value;
}
// Add any new runners, because a staller timed out or a result
// or error response came in.
for (let i = 0; i < newRunners; i++) {
this.#addRunner(running, req);
}
// All providers have returned, and we have no result
assert(interesting.length > 0, "quorum not met", "SERVER_ERROR", {
request: "%sub-requests",
info: { request: req, results: Array.from(running).map((r) => stringify(r.result)) }
});
// Wait for someone to either complete its perform or stall out
await Promise.race(interesting);
// This is recursive, but at worst case the depth is 2x the
// number of providers (each has a perform and a staller)
return await this.#waitForQuorum(running, req);
}
async _perform<T = any>(req: PerformActionRequest): Promise<T> {
// Broadcasting a transaction is rare (ish) and already incurs
// a cost on the user, so spamming is safe-ish. Just send it to
// every backend.
if (req.method === "broadcastTransaction") {
// Once any broadcast provides a positive result, use it. No
// need to wait for anyone else
const results: Array<null | TallyResult> = this.#configs.map((c) => null);
const broadcasts = this.#configs.map(async ({ provider, weight }, index) => {
try {
const result = await provider._perform(req);
results[index] = Object.assign(normalizeResult(req.method, { result }), { weight });
} catch (error: any) {
results[index] = Object.assign(normalizeResult(req.method, { error }), { weight });
}
});
// As each promise finishes...
while (true) {
// Check for a valid broadcast result
const done = <Array<any>>results.filter((r) => (r != null));
for (const { value } of done) {
if (!(value instanceof Error)) { return value; }
}
// Check for a legit broadcast error (one which we cannot
// recover from; some nodes may return the following red
// herring events:
// - alredy seend (UNKNOWN_ERROR)
// - NONCE_EXPIRED
// - REPLACEMENT_UNDERPRICED
const result = checkQuorum(this.quorum, <Array<any>>results.filter((r) => (r != null)));
if (isError(result, "INSUFFICIENT_FUNDS")) {
throw result;
}
// Kick off the next provider (if any)
const waiting = broadcasts.filter((b, i) => (results[i] == null));
if (waiting.length === 0) { break; }
await Promise.race(waiting);
}
// Use standard quorum results; any result was returned above,
// so this will find any error that met quorum if any
const result = getAnyResult(this.quorum, <Array<any>>results);
assert(result !== undefined, "problem multi-broadcasting", "SERVER_ERROR", {
request: "%sub-requests",
info: { request: req, results: results.map(stringify) }
})
if (result instanceof Error) { throw result; }
return result;
}
await this.#initialSync();
// Bootstrap enough runners to meet quorum
const running: Set<RunnerState> = new Set();
let inflightQuorum = 0;
while (true) {
const runner = this.#addRunner(running, req);
if (runner == null) { break; }
inflightQuorum += runner.config.weight;
if (inflightQuorum >= this.quorum) { break; }
}
const result = await this.#waitForQuorum(running, req);
// Track requests sent to a provider that are still
// outstanding after quorum has been otherwise found
for (const runner of running) {
if (runner.perform && runner.result == null) {
runner.config.lateResponses++;
}
}
return result;
}
async destroy(): Promise<void> {
for (const { provider } of this.#configs) {
provider.destroy();
}
super.destroy();
}
}

View File

@@ -0,0 +1,220 @@
/**
* [[link-infura]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Sepolia Testnet (``sepolia``)
* - Arbitrum (``arbitrum``)
* - Arbitrum Goerli Testnet (``arbitrum-goerli``)
* - Arbitrum Sepolia Testnet (``arbitrum-sepolia``)
* - Base (``base``)
* - Base Goerlia Testnet (``base-goerli``)
* - Base Sepolia Testnet (``base-sepolia``)
* - BNB Smart Chain Mainnet (``bnb``)
* - BNB Smart Chain Testnet (``bnbt``)
* - Linea (``linea``)
* - Linea Goerli Testnet (``linea-goerli``)
* - Linea Sepolia Testnet (``linea-sepolia``)
* - Optimism (``optimism``)
* - Optimism Goerli Testnet (``optimism-goerli``)
* - Optimism Sepolia Testnet (``optimism-sepolia``)
* - Polygon (``matic``)
* - Polygon Amoy Testnet (``matic-amoy``)
* - Polygon Mumbai Testnet (``matic-mumbai``)
*
* @_subsection: api/providers/thirdparty:INFURA [providers-infura]
*/
import {
defineProperties, FetchRequest, assert, assertArgument
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import { WebSocketProvider } from "./provider-websocket.js";
import type { AbstractProvider } from "./abstract-provider.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
const defaultProjectId = "84842078b09946638c03157f83405213";
function getHost(name: string): string {
switch(name) {
case "mainnet":
return "mainnet.infura.io";
case "goerli":
return "goerli.infura.io";
case "sepolia":
return "sepolia.infura.io";
case "arbitrum":
return "arbitrum-mainnet.infura.io";
case "arbitrum-goerli":
return "arbitrum-goerli.infura.io";
case "arbitrum-sepolia":
return "arbitrum-sepolia.infura.io";
case "base":
return "base-mainnet.infura.io";
case "base-goerlia": // @TODO: Remove this typo in the future!
case "base-goerli":
return "base-goerli.infura.io";
case "base-sepolia":
return "base-sepolia.infura.io";
case "bnb":
return "bsc-mainnet.infura.io";
case "bnbt":
return "bsc-testnet.infura.io";
case "linea":
return "linea-mainnet.infura.io";
case "linea-goerli":
return "linea-goerli.infura.io";
case "linea-sepolia":
return "linea-sepolia.infura.io";
case "matic":
return "polygon-mainnet.infura.io";
case "matic-amoy":
return "polygon-amoy.infura.io";
case "matic-mumbai":
return "polygon-mumbai.infura.io";
case "optimism":
return "optimism-mainnet.infura.io";
case "optimism-goerli":
return "optimism-goerli.infura.io";
case "optimism-sepolia":
return "optimism-sepolia.infura.io";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **InfuraWebSocketProvider** connects to the [[link-infura]]
* WebSocket end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-infura-signup).
*/
export class InfuraWebSocketProvider extends WebSocketProvider implements CommunityResourcable {
/**
* The Project ID for the INFURA connection.
*/
readonly projectId!: string;
/**
* The Project Secret.
*
* If null, no authenticated requests are made. This should not
* be used outside of private contexts.
*/
readonly projectSecret!: null | string;
/**
* Creates a new **InfuraWebSocketProvider**.
*/
constructor(network?: Networkish, projectId?: string) {
const provider = new InfuraProvider(network, projectId);
const req = provider._getConnection();
assert(!req.credentials, "INFURA WebSocket project secrets unsupported",
"UNSUPPORTED_OPERATION", { operation: "InfuraProvider.getWebSocketProvider()" });
const url = req.url.replace(/^http/i, "ws").replace("/v3/", "/ws/v3/");
super(url, provider._network);
defineProperties<InfuraWebSocketProvider>(this, {
projectId: provider.projectId,
projectSecret: provider.projectSecret
});
}
isCommunityResource(): boolean {
return (this.projectId === defaultProjectId);
}
}
/**
* The **InfuraProvider** connects to the [[link-infura]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-infura-signup).
*/
export class InfuraProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The Project ID for the INFURA connection.
*/
readonly projectId!: string;
/**
* The Project Secret.
*
* If null, no authenticated requests are made. This should not
* be used outside of private contexts.
*/
readonly projectSecret!: null | string;
/**
* Creates a new **InfuraProvider**.
*/
constructor(_network?: Networkish, projectId?: null | string, projectSecret?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (projectId == null) { projectId = defaultProjectId; }
if (projectSecret == null) { projectSecret = null; }
const request = InfuraProvider.getRequest(network, projectId, projectSecret);
super(request, network, { staticNetwork: network });
defineProperties<InfuraProvider>(this, { projectId, projectSecret });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new InfuraProvider(chainId, this.projectId, this.projectSecret);
} catch (error) { }
return super._getProvider(chainId);
}
isCommunityResource(): boolean {
return (this.projectId === defaultProjectId);
}
/**
* Creates a new **InfuraWebSocketProvider**.
*/
static getWebSocketProvider(network?: Networkish, projectId?: string): InfuraWebSocketProvider {
return new InfuraWebSocketProvider(network, projectId);
}
/**
* Returns a prepared request for connecting to %%network%%
* with %%projectId%% and %%projectSecret%%.
*/
static getRequest(network: Network, projectId?: null | string, projectSecret?: null | string): FetchRequest {
if (projectId == null) { projectId = defaultProjectId; }
if (projectSecret == null) { projectSecret = null; }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/v3/${ projectId }`);
request.allowGzip = true;
if (projectSecret) { request.setCredentials("", projectSecret); }
if (projectId === defaultProjectId) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("InfuraProvider");
return true;
};
}
return request;
}
}

View File

@@ -0,0 +1,3 @@
const IpcSocketProvider = undefined;
export { IpcSocketProvider };

View File

@@ -0,0 +1,81 @@
import { connect } from "net";
import { SocketProvider } from "./provider-socket.js";
import type { Socket } from "net";
import type { JsonRpcApiProviderOptions } from "./provider-jsonrpc.js";
import type { Networkish } from "./network.js";
// @TODO: Is this sufficient? Is this robust? Will newlines occur between
// all payloads and only between payloads?
function splitBuffer(data: Buffer): { messages: Array<string>, remaining: Buffer } {
const messages: Array<string> = [ ];
let lastStart = 0;
while (true) {
const nl = data.indexOf(10, lastStart);
if (nl === -1) { break; }
messages.push(data.subarray(lastStart, nl).toString().trim());
lastStart = nl + 1;
}
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, options?: JsonRpcApiProviderOptions) {
super(network, options);
this.#socket = connect(path);
this.socket.on("ready", async () => {
try {
await this._start();
} catch (error) {
console.log("failed to start IpcSocketProvider", error);
// @TODO: Now what? Restart?
}
});
let response = Buffer.alloc(0);
this.socket.on("data", (data) => {
response = Buffer.concat([ response, data ]);
const { messages, remaining } = splitBuffer(response);
messages.forEach((message) => {
this._processMessage(message);
});
response = remaining;
});
this.socket.on("end", () => {
this.emit("close");
this.socket.destroy();
this.socket.end();
});
}
destroy(): void {
this.socket.destroy();
this.socket.end();
super.destroy();
}
async _write(message: string): Promise<void> {
if (!message.endsWith("\n")) { message += "\n"; }
this.socket.write(message);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
/**
* [[link-pocket]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Polygon (``matic``)
* - Arbitrum (``arbitrum``)
*
* @_subsection: api/providers/thirdparty:Pocket [providers-pocket]
*/
import {
defineProperties, FetchRequest, assertArgument
} from "../utils/index.js";
import { AbstractProvider } from "./abstract-provider.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
const defaultApplicationId = "62e1ad51b37b8e00394bda3b";
function getHost(name: string): string {
switch (name) {
case "mainnet":
return "eth-mainnet.gateway.pokt.network";
case "goerli":
return "eth-goerli.gateway.pokt.network";
case "matic":
return "poly-mainnet.gateway.pokt.network";
case "matic-mumbai":
return "polygon-mumbai-rpc.gateway.pokt.network";
}
assertArgument(false, "unsupported network", "network", name);
}
/**
* The **PocketProvider** connects to the [[link-pocket]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API key is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-pocket-signup).
*/
export class PocketProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The Application ID for the Pocket connection.
*/
readonly applicationId!: string;
/**
* The Application Secret for making authenticated requests
* to the Pocket connection.
*/
readonly applicationSecret!: null | string;
/**
* Create a new **PocketProvider**.
*
* By default connecting to ``mainnet`` with a highly throttled
* API key.
*/
constructor(_network?: Networkish, applicationId?: null | string, applicationSecret?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (applicationId == null) { applicationId = defaultApplicationId; }
if (applicationSecret == null) { applicationSecret = null; }
const options = { staticNetwork: network };
const request = PocketProvider.getRequest(network, applicationId, applicationSecret);
super(request, network, options);
defineProperties<PocketProvider>(this, { applicationId, applicationSecret });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new PocketProvider(chainId, this.applicationId, this.applicationSecret);
} catch (error) { }
return super._getProvider(chainId);
}
/**
* Returns a prepared request for connecting to %%network%% with
* %%applicationId%%.
*/
static getRequest(network: Network, applicationId?: null | string, applicationSecret?: null | string): FetchRequest {
if (applicationId == null) { applicationId = defaultApplicationId; }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/v1/lb/${ applicationId }`);
request.allowGzip = true;
if (applicationSecret) {
request.setCredentials("", applicationSecret);
}
if (applicationId === defaultApplicationId) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("PocketProvider");
return true;
};
}
return request;
}
isCommunityResource(): boolean {
return (this.applicationId === defaultApplicationId);
}
}

View File

@@ -0,0 +1,177 @@
/**
* [[link-quicknode]] provides a third-party service for connecting to
* various blockchains over JSON-RPC.
*
* **Supported Networks**
*
* - Ethereum Mainnet (``mainnet``)
* - Goerli Testnet (``goerli``)
* - Sepolia Testnet (``sepolia``)
* - Holesky Testnet (``holesky``)
* - Arbitrum (``arbitrum``)
* - Arbitrum Goerli Testnet (``arbitrum-goerli``)
* - Arbitrum Sepolia Testnet (``arbitrum-sepolia``)
* - Base Mainnet (``base``);
* - Base Goerli Testnet (``base-goerli``);
* - Base Sepolia Testnet (``base-sepolia``);
* - BNB Smart Chain Mainnet (``bnb``)
* - BNB Smart Chain Testnet (``bnbt``)
* - Optimism (``optimism``)
* - Optimism Goerli Testnet (``optimism-goerli``)
* - Optimism Sepolia Testnet (``optimism-sepolia``)
* - Polygon (``matic``)
* - Polygon Mumbai Testnet (``matic-mumbai``)
*
* @_subsection: api/providers/thirdparty:QuickNode [providers-quicknode]
*/
import {
defineProperties, FetchRequest, assertArgument
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js";
import { Network } from "./network.js";
import { JsonRpcProvider } from "./provider-jsonrpc.js";
import type { AbstractProvider } from "./abstract-provider.js";
import type { CommunityResourcable } from "./community.js";
import type { Networkish } from "./network.js";
const defaultToken = "919b412a057b5e9c9b6dce193c5a60242d6efadb";
function getHost(name: string): string {
switch(name) {
case "mainnet":
return "ethers.quiknode.pro";
case "goerli":
return "ethers.ethereum-goerli.quiknode.pro";
case "sepolia":
return "ethers.ethereum-sepolia.quiknode.pro";
case "holesky":
return "ethers.ethereum-holesky.quiknode.pro";
case "arbitrum":
return "ethers.arbitrum-mainnet.quiknode.pro";
case "arbitrum-goerli":
return "ethers.arbitrum-goerli.quiknode.pro";
case "arbitrum-sepolia":
return "ethers.arbitrum-sepolia.quiknode.pro";
case "base":
return "ethers.base-mainnet.quiknode.pro";
case "base-goerli":
return "ethers.base-goerli.quiknode.pro";
case "base-spolia":
return "ethers.base-sepolia.quiknode.pro";
case "bnb":
return "ethers.bsc.quiknode.pro";
case "bnbt":
return "ethers.bsc-testnet.quiknode.pro";
case "matic":
return "ethers.matic.quiknode.pro";
case "matic-mumbai":
return "ethers.matic-testnet.quiknode.pro";
case "optimism":
return "ethers.optimism.quiknode.pro";
case "optimism-goerli":
return "ethers.optimism-goerli.quiknode.pro";
case "optimism-sepolia":
return "ethers.optimism-sepolia.quiknode.pro";
case "xdai":
return "ethers.xdai.quiknode.pro";
}
assertArgument(false, "unsupported network", "network", name);
}
/*
@TODO:
These networks are not currently present in the Network
default included networks. Research them and ensure they
are EVM compatible and work with ethers
http://ethers.matic-amoy.quiknode.pro
http://ethers.avalanche-mainnet.quiknode.pro
http://ethers.avalanche-testnet.quiknode.pro
http://ethers.blast-sepolia.quiknode.pro
http://ethers.celo-mainnet.quiknode.pro
http://ethers.fantom.quiknode.pro
http://ethers.imx-demo.quiknode.pro
http://ethers.imx-mainnet.quiknode.pro
http://ethers.imx-testnet.quiknode.pro
http://ethers.near-mainnet.quiknode.pro
http://ethers.near-testnet.quiknode.pro
http://ethers.nova-mainnet.quiknode.pro
http://ethers.scroll-mainnet.quiknode.pro
http://ethers.scroll-testnet.quiknode.pro
http://ethers.tron-mainnet.quiknode.pro
http://ethers.zkevm-mainnet.quiknode.pro
http://ethers.zkevm-testnet.quiknode.pro
http://ethers.zksync-mainnet.quiknode.pro
http://ethers.zksync-testnet.quiknode.pro
*/
/**
* The **QuickNodeProvider** connects to the [[link-quicknode]]
* JSON-RPC end-points.
*
* By default, a highly-throttled API token is used, which is
* appropriate for quick prototypes and simple scripts. To
* gain access to an increased rate-limit, it is highly
* recommended to [sign up here](link-quicknode).
*/
export class QuickNodeProvider extends JsonRpcProvider implements CommunityResourcable {
/**
* The API token.
*/
readonly token!: string;
/**
* Creates a new **QuickNodeProvider**.
*/
constructor(_network?: Networkish, token?: null | string) {
if (_network == null) { _network = "mainnet"; }
const network = Network.from(_network);
if (token == null) { token = defaultToken; }
const request = QuickNodeProvider.getRequest(network, token);
super(request, network, { staticNetwork: network });
defineProperties<QuickNodeProvider>(this, { token });
}
_getProvider(chainId: number): AbstractProvider {
try {
return new QuickNodeProvider(chainId, this.token);
} catch (error) { }
return super._getProvider(chainId);
}
isCommunityResource(): boolean {
return (this.token === defaultToken);
}
/**
* Returns a new request prepared for %%network%% and the
* %%token%%.
*/
static getRequest(network: Network, token?: null | string): FetchRequest {
if (token == null) { token = defaultToken; }
const request = new FetchRequest(`https:/\/${ getHost(network.name) }/${ token }`);
request.allowGzip = true;
//if (projectSecret) { request.setCredentials("", projectSecret); }
if (token === defaultToken) {
request.retryFunc = async (request, response, attempt) => {
showThrottleMessage("QuickNodeProvider");
return true;
};
}
return request;
}
}

View File

@@ -0,0 +1,352 @@
/**
* Generic long-lived socket provider.
*
* Sub-classing notes
* - a sub-class MUST call the `_start()` method once connected
* - a sub-class MUST override the `_write(string)` method
* - a sub-class MUST call `_processMessage(string)` for each message
*
* @_subsection: api/providers/abstract-provider:Socket Providers [about-socketProvider]
*/
import { UnmanagedSubscriber } from "./abstract-provider.js";
import { assert, assertArgument, makeError } from "../utils/index.js";
import { JsonRpcApiProvider } from "./provider-jsonrpc.js";
import type { Subscriber, Subscription } from "./abstract-provider.js";
import type { EventFilter } from "./provider.js";
import type {
JsonRpcApiProviderOptions, JsonRpcError, JsonRpcPayload, JsonRpcResult
} from "./provider-jsonrpc.js";
import type { Networkish } from "./network.js";
type JsonRpcSubscription = {
method: string,
params: {
result: any,
subscription: string
}
};
/**
* 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>;
#paused: null | boolean;
#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);
this.#filterId = null;
this.#paused = null;
this.#emitPromise = null;
}
start(): void {
this.#filterId = this.#provider.send("eth_subscribe", this.filter).then((filterId) => {;
this.#provider._register(filterId, this);
return filterId;
});
}
stop(): void {
(<Promise<number>>(this.#filterId)).then((filterId) => {
if (this.#provider.destroyed) { return; }
this.#provider.send("eth_unsubscribe", [ filterId ]);
});
this.#filterId = null;
}
// @TODO: pause should trap the current blockNumber, unsub, and on resume use getLogs
// and resume
pause(dropWhilePaused?: boolean): void {
assert(dropWhilePaused, "preserve logs while paused not supported by SocketSubscriber yet",
"UNSUPPORTED_OPERATION", { operation: "pause(false)" });
this.#paused = !!dropWhilePaused;
}
resume(): void {
this.#paused = null;
}
/**
* @_ignore:
*/
_handleMessage(message: any): void {
if (this.#filterId == null) { return; }
if (this.#paused === null) {
let emitPromise: null | Promise<void> = this.#emitPromise;
if (emitPromise == null) {
emitPromise = this._emit(this.#provider, message);
} else {
emitPromise = emitPromise.then(async () => {
await this._emit(this.#provider, message);
});
}
this.#emitPromise = emitPromise.then(() => {
if (this.#emitPromise === emitPromise) {
this.#emitPromise = null;
}
});
}
}
/**
* 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" ]);
}
async _emit(provider: SocketProvider, message: any): Promise<void> {
provider.emit("block", parseInt(message.number));
}
}
/**
* A **SocketPendingSubscriber** listens for pending transacitons and emits
* ``"pending"`` events.
*/
export class SocketPendingSubscriber extends SocketSubscriber {
/**
* @_ignore:
*/
constructor(provider: SocketProvider) {
super(provider, [ "newPendingTransactions" ]);
}
async _emit(provider: SocketProvider, message: any): Promise<void> {
provider.emit("pending", message);
}
}
/**
* 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);
}
async _emit(provider: SocketProvider, message: any): Promise<void> {
provider.emit(this.logFilter, provider._wrapLog(message, provider._network));
}
}
/**
* 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 }>;
// Maps each filterId to its subscriber
#subs: Map<number | string, SocketSubscriber>;
// If any events come in before a subscriber has finished
// 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, _options?: JsonRpcApiProviderOptions) {
// Copy the options
const options = Object.assign({ }, (_options != null) ? _options: { });
// Support for batches is generally not supported for
// connection-base providers; if this changes in the future
// the _send should be updated to reflect this
assertArgument(options.batchMaxCount == null || options.batchMaxCount === 1,
"sockets-based providers do not support batches", "options.batchMaxCount", _options);
options.batchMaxCount = 1;
// Socket-based Providers (generally) cannot change their network,
// since they have a long-lived connection; but let people override
// this if they have just cause.
if (options.staticNetwork == null) { options.staticNetwork = true; }
super(network, options);
this.#callbacks = new Map();
this.#subs = new Map();
this.#pending = new Map();
}
// This value is only valid after _start has been called
/*
get _network(): Network {
if (this.#network == null) {
throw new Error("this shouldn't happen");
}
return this.#network.clone();
}
*/
_getSubscriber(sub: Subscription): Subscriber {
switch (sub.type) {
case "close":
return new UnmanagedSubscriber("close");
case "block":
return new SocketBlockSubscriber(this);
case "pending":
return new SocketPendingSubscriber(this);
case "event":
return new SocketEventSubscriber(this, sub.filter);
case "orphan":
// Handled auto-matically within AbstractProvider
// when the log.removed = true
if (sub.filter.orphan === "drop-log") {
return new UnmanagedSubscriber("drop-log");
}
}
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);
if (pending) {
for (const message of pending) {
subscriber._handleMessage(message);
}
this.#pending.delete(filterId);
}
}
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult | JsonRpcError>> {
// WebSocket provider doesn't accept batches
assertArgument(!Array.isArray(payload), "WebSocket does not support batch send", "payload", payload);
// @TODO: stringify payloads here and store to prevent mutations
// Prepare a promise to respond to
const promise = new Promise((resolve, reject) => {
this.#callbacks.set(payload.id, { payload, resolve, reject });
});
// Wait until the socket is connected before writing to it
await this._waitUntilReady();
// Write the request to the socket
await this._write(JSON.stringify(payload));
return <Array<JsonRpcResult | JsonRpcError>>[ await promise ];
}
// Sub-classes must call this once they are connected
/*
async _start(): Promise<void> {
if (this.#ready) { return; }
for (const { payload } of this.#callbacks.values()) {
await this._write(JSON.stringify(payload));
}
this.#ready = (async function() {
await super._start();
})();
}
*/
/**
* 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));
if (result && typeof(result) === "object" && "id" in result) {
const callback = this.#callbacks.get(result.id);
if (callback == null) {
this.emit("error", makeError("received result for unknown id", "UNKNOWN_ERROR", {
reasonCode: "UNKNOWN_ID",
result
}));
return;
}
this.#callbacks.delete(result.id);
callback.resolve(result);
} else if (result && result.method === "eth_subscription") {
const filterId = result.params.subscription;
const subscriber = this.#subs.get(filterId);
if (subscriber) {
subscriber._handleMessage(result.params.result);
} else {
let pending = this.#pending.get(filterId);
if (pending == null) {
pending = [ ];
this.#pending.set(filterId, pending);
}
pending.push(result.params.result);
}
} else {
this.emit("error", makeError("received unexpected message", "UNKNOWN_ERROR", {
reasonCode: "UNEXPECTED_MESSAGE",
result
}));
return;
}
}
/**
* 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");
}
}

View File

@@ -0,0 +1,103 @@
import { WebSocket as _WebSocket } from "./ws.js"; /*-browser*/
import { SocketProvider } from "./provider-socket.js";
import type { JsonRpcApiProviderOptions} from "./provider-jsonrpc.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);
onerror: null | ((...args: Array<any>) => any);
readyState: number;
send(payload: any): void;
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;
#websocket: null | WebSocketLike;
get websocket(): WebSocketLike {
if (this.#websocket == null) { throw new Error("websocket closed"); }
return this.#websocket;
}
constructor(url: string | WebSocketLike | WebSocketCreator, network?: Networkish, options?: JsonRpcApiProviderOptions) {
super(network, options);
if (typeof(url) === "string") {
this.#connect = () => { return new _WebSocket(url); };
this.#websocket = this.#connect();
} else if (typeof(url) === "function") {
this.#connect = url;
this.#websocket = url();
} else {
this.#connect = null;
this.#websocket = url;
}
this.websocket.onopen = async () => {
try {
await this._start()
this.resume();
} catch (error) {
console.log("failed to start WebsocketProvider", error);
// @TODO: now what? Attempt reconnect?
}
};
this.websocket.onmessage = (message: { data: string }) => {
this._processMessage(message.data);
};
/*
this.websocket.onclose = (event) => {
// @TODO: What event.code should we reconnect on?
const reconnect = false;
if (reconnect) {
this.pause(true);
if (this.#connect) {
this.#websocket = this.#connect();
this.#websocket.onopen = ...
// @TODO: this requires the super class to rebroadcast; move it there
}
this._reconnect();
}
};
*/
}
async _write(message: string): Promise<void> {
this.websocket.send(message);
}
async destroy(): Promise<void> {
if (this.#websocket != null) {
this.#websocket.close();
this.#websocket = null;
}
super.destroy();
}
}

2146
dev/env/node_modules/ethers/src.ts/providers/provider.ts generated vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
import { defineProperties } from "../utils/index.js";
import { AbstractSigner } from "./abstract-signer.js";
import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
import type {
BlockTag, Provider, TransactionRequest, TransactionResponse
} from "./provider.js";
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 });
this.#noncePromise = null;
this.#delta = 0;
}
async getAddress(): Promise<string> {
return this.signer.getAddress();
}
connect(provider: null | Provider): NonceManager {
return new NonceManager(this.signer.connect(provider));
}
async getNonce(blockTag?: BlockTag): Promise<number> {
if (blockTag === "pending") {
if (this.#noncePromise == null) {
this.#noncePromise = super.getNonce("pending");
}
const delta = this.#delta;
return (await this.#noncePromise) + delta;
}
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;
}
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
const noncePromise = this.getNonce("pending");
this.increment();
tx = await this.signer.populateTransaction(tx);
tx.nonce = await noncePromise;
// @TODO: Maybe handle interesting/recoverable errors?
// Like don't increment if the tx was certainly not sent
return await this.signer.sendTransaction(tx);
}
signTransaction(tx: TransactionRequest): Promise<string> {
return this.signer.signTransaction(tx);
}
signMessage(message: string | Uint8Array): Promise<string> {
return this.signer.signMessage(message);
}
signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
return this.signer.signTypedData(domain, types, value);
}
}

166
dev/env/node_modules/ethers/src.ts/providers/signer.ts generated vendored Executable file
View File

@@ -0,0 +1,166 @@
import type { Addressable, NameResolver } from "../address/index.js";
import type {
AuthorizationRequest, TypedDataDomain, TypedDataField
} from "../hash/index.js";
import type { Authorization, TransactionLike } from "../transaction/index.js";
import type { ContractRunner } from "./contracts.js";
import type { BlockTag, Provider, TransactionRequest, TransactionResponse } from "./provider.js";
/**
* A Signer represents an account on the Ethereum Blockchain, and is most often
* backed by a private key represented by a mnemonic or residing on a Hardware Wallet.
*
* The API remains abstract though, so that it can deal with more advanced exotic
* Signing entities, such as Smart Contract Wallets or Virtual Wallets (where the
* private key may not be known).
*/
export interface Signer extends Addressable, ContractRunner, NameResolver {
/**
* The [[Provider]] attached to this Signer (if any).
*/
provider: null | Provider;
/**
* Returns a new instance of this Signer connected to //provider// or detached
* from any Provider if null.
*/
connect(provider: null | Provider): Signer;
////////////////////
// State
/**
* Get the address of the Signer.
*/
getAddress(): Promise<string>;
/**
* Gets the next nonce required for this Signer to send a transaction.
*
* @param blockTag - The blocktag to base the transaction count on, keep in mind
* many nodes do not honour this value and silently ignore it [default: ``"latest"``]
*/
getNonce(blockTag?: BlockTag): Promise<number>;
////////////////////
// Preparation
/**
* Prepares a {@link TransactionRequest} for calling:
* - resolves ``to`` and ``from`` addresses
* - if ``from`` is specified , check that it matches this Signer
*
* @param tx - The call to prepare
*/
populateCall(tx: TransactionRequest): Promise<TransactionLike<string>>;
/**
* Prepares a {@link TransactionRequest} for sending to the network by
* populating any missing properties:
* - resolves ``to`` and ``from`` addresses
* - if ``from`` is specified , check that it matches this Signer
* - populates ``nonce`` via ``signer.getNonce("pending")``
* - populates ``gasLimit`` via ``signer.estimateGas(tx)``
* - populates ``chainId`` via ``signer.provider.getNetwork()``
* - populates ``type`` and relevant fee data for that type (``gasPrice``
* for legacy transactions, ``maxFeePerGas`` for EIP-1559, etc)
*
* @note Some Signer implementations may skip populating properties that
* are populated downstream; for example JsonRpcSigner defers to the
* node to populate the nonce and fee data.
*
* @param tx - The call to prepare
*/
populateTransaction(tx: TransactionRequest): Promise<TransactionLike<string>>;
////////////////////
// Execution
/**
* Estimates the required gas required to execute //tx// on the Blockchain. This
* will be the expected amount a transaction will require as its ``gasLimit``
* to successfully run all the necessary computations and store the needed state
* that the transaction intends.
*
* Keep in mind that this is **best efforts**, since the state of the Blockchain
* is in flux, which could affect transaction gas requirements.
*
* @throws UNPREDICTABLE_GAS_LIMIT A transaction that is believed by the node to likely
* fail will throw an error during gas estimation. This could indicate that it
* will actually fail or that the circumstances are simply too complex for the
* node to take into account. In these cases, a manually determined ``gasLimit``
* will need to be made.
*/
estimateGas(tx: TransactionRequest): Promise<bigint>;
/**
* Evaluates the //tx// by running it against the current Blockchain state. This
* cannot change state and has no cost in ether, as it is effectively simulating
* execution.
*
* This can be used to have the Blockchain perform computations based on its state
* (e.g. running a Contract's getters) or to simulate the effect of a transaction
* before actually performing an operation.
*/
call(tx: TransactionRequest): Promise<string>;
/**
* Resolves an ENS Name to an address.
*/
resolveName(name: string): Promise<null | string>;
////////////////////
// Signing
/**
* Signs %%tx%%, returning the fully signed transaction. This does not
* populate any additional properties within the transaction.
*/
signTransaction(tx: TransactionRequest): Promise<string>;
/**
* Sends %%tx%% to the Network. The ``signer.populateTransaction(tx)``
* is called first to ensure all necessary properties for the
* transaction to be valid have been popualted first.
*/
sendTransaction(tx: TransactionRequest): Promise<TransactionResponse>;
/**
* Signs an [[link-eip-191]] prefixed personal message.
*
* If the %%message%% is a string, it is signed as UTF-8 encoded bytes. It is **not**
* interpretted as a [[BytesLike]]; so the string ``"0x1234"`` is signed as six
* characters, **not** two bytes.
*
* To sign that example as two bytes, the Uint8Array should be used
* (i.e. ``new Uint8Array([ 0x12, 0x34 ])``).
*/
signMessage(message: string | Uint8Array): Promise<string>;
/**
* Signs the [[link-eip-712]] typed data.
*/
signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
/**
* Prepares an [[AuthorizationRequest]] for authorization by
* populating any missing properties:
* - resolves ``address`` (if an Addressable or ENS name)
* - populates ``nonce`` via ``signer.getNonce("pending")``
* - populates ``chainId`` via ``signer.provider.getNetwork()``
*/
populateAuthorization(auth: AuthorizationRequest): Promise<AuthorizationRequest>;
/**
* Signs an %%authorization%% to be used in [[link-eip-7702]]
* transactions.
*/
authorize(authorization: AuthorizationRequest): Promise<Authorization>;
}

View File

@@ -0,0 +1,74 @@
import { getNumber } from "../utils/index.js";
import type { Subscriber } from "./abstract-provider.js";
//#TODO: Temp
import type { Provider } from "./provider.js";
/**
* @TODO
*
* @_docloc: api/providers/abstract-provider
*/
export interface ConnectionRpcProvider extends Provider {
//send(method: string, params: Array<any>): Promise<any>;
_subscribe(param: Array<any>, processFunc: (result: any) => void): number;
_unsubscribe(filterId: number): void;
}
/**
* @TODO
*
* @_docloc: api/providers/abstract-provider
*/
export class BlockConnectionSubscriber implements Subscriber {
#provider: ConnectionRpcProvider;
#blockNumber: number;
#running: boolean;
#filterId: null | number;
constructor(provider: ConnectionRpcProvider) {
this.#provider = provider;
this.#blockNumber = -2;
this.#running = false;
this.#filterId = null;
}
start(): void {
if (this.#running) { return; }
this.#running = true;
this.#filterId = this.#provider._subscribe([ "newHeads" ], (result: any) => {
const blockNumber = getNumber(result.number);
const initial = (this.#blockNumber === -2) ? blockNumber: (this.#blockNumber + 1)
for (let b = initial; b <= blockNumber; b++) {
this.#provider.emit("block", b);
}
this.#blockNumber = blockNumber;
});
}
stop(): void {
if (!this.#running) { return; }
this.#running = false;
if (this.#filterId != null) {
this.#provider._unsubscribe(this.#filterId);
this.#filterId = null;
}
}
pause(dropWhilePaused?: boolean): void {
if (dropWhilePaused) { this.#blockNumber = -2; }
this.stop();
}
resume(): void {
this.start();
}
}

View File

@@ -0,0 +1,199 @@
import { isError } from "../utils/index.js";
import { PollingEventSubscriber } from "./subscriber-polling.js";
import type { AbstractProvider, Subscriber } from "./abstract-provider.js";
import type { Network } from "./network.js";
import type { EventFilter } from "./provider.js";
import type { JsonRpcApiProvider } from "./provider-jsonrpc.js";
function copy(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
/**
* Some backends support subscribing to events using a Filter ID.
*
* When subscribing with this technique, the node issues a unique
* //Filter ID//. At this point the node dedicates resources to
* the filter, so that periodic calls to follow up on the //Filter ID//
* will receive any events since the last call.
*
* @_docloc: api/providers/abstract-provider
*/
export class FilterIdSubscriber implements Subscriber {
#provider: JsonRpcApiProvider;
#filterIdPromise: null | Promise<string>;
#poller: (b: number) => Promise<void>;
#running: boolean;
#network: null | Network;
#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;
this.#filterIdPromise = null;
this.#poller = this.#poll.bind(this);
this.#running = false;
this.#network = null;
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");
}
async #poll(blockNumber: number): Promise<void> {
try {
// Subscribe if necessary
if (this.#filterIdPromise == null) {
this.#filterIdPromise = this._subscribe(this.#provider);
}
// Get the Filter ID
let filterId: null | string = null;
try {
filterId = await this.#filterIdPromise;
} catch (error) {
if (!isError(error, "UNSUPPORTED_OPERATION") || error.operation !== "eth_newFilter") {
throw error;
}
}
// The backend does not support Filter ID; downgrade to
// polling
if (filterId == null) {
this.#filterIdPromise = null;
this.#provider._recoverSubscriber(this, this._recover(this.#provider));
return;
}
const network = await this.#provider.getNetwork();
if (!this.#network) { this.#network = network; }
if ((this.#network as Network).chainId !== network.chainId) {
throw new Error("chaid changed");
}
if (this.#hault) { return; }
const result = await this.#provider.send("eth_getFilterChanges", [ filterId ]);
await this._emitResults(this.#provider, result);
} catch (error) { console.log("@TODO", error); }
this.#provider.once("block", this.#poller);
}
#teardown(): void {
const filterIdPromise = this.#filterIdPromise;
if (filterIdPromise) {
this.#filterIdPromise = null;
filterIdPromise.then((filterId) => {
if (this.#provider.destroyed) { return; }
this.#provider.send("eth_uninstallFilter", [ filterId ]);
});
}
}
start(): void {
if (this.#running) { return; }
this.#running = true;
this.#poll(-2);
}
stop(): void {
if (!this.#running) { return; }
this.#running = false;
this.#hault = true;
this.#teardown();
this.#provider.off("block", this.#poller);
}
pause(dropWhilePaused?: boolean): void {
if (dropWhilePaused){ this.#teardown(); }
this.#provider.off("block", this.#poller);
}
resume(): void { this.start(); }
}
/**
* A **FilterIdSubscriber** for receiving contract events.
*
* @_docloc: api/providers/abstract-provider
*/
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);
}
_recover(provider: AbstractProvider): Subscriber {
return new PollingEventSubscriber(provider, this.#event);
}
async _subscribe(provider: JsonRpcApiProvider): Promise<string> {
const filterId = await provider.send("eth_newFilter", [ this.#event ]);
return filterId;
}
async _emitResults(provider: JsonRpcApiProvider, results: Array<any>): Promise<void> {
for (const result of results) {
provider.emit(this.#event, provider._wrapLog(result, provider._network));
}
}
}
/**
* A **FilterIdSubscriber** for receiving pending transactions events.
*
* @_docloc: api/providers/abstract-provider
*/
export class FilterIdPendingSubscriber extends FilterIdSubscriber {
async _subscribe(provider: JsonRpcApiProvider): Promise<string> {
return await provider.send("eth_newPendingTransactionFilter", [ ]);
}
async _emitResults(provider: JsonRpcApiProvider, results: Array<any>): Promise<void> {
for (const result of results) {
provider.emit("pending", result);
}
}
}

View File

@@ -0,0 +1,321 @@
import { assert, isHexString } from "../utils/index.js";
import type { AbstractProvider, Subscriber } from "./abstract-provider.js";
import type { EventFilter, OrphanFilter, ProviderEvent } from "./provider.js";
function copy(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
/**
* Return the polling subscriber for common events.
*
* @_docloc: api/providers/abstract-provider
*/
export function getPollingSubscriber(provider: AbstractProvider, event: ProviderEvent): Subscriber {
if (event === "block") { return new PollingBlockSubscriber(provider); }
if (isHexString(event, 32)) { return new PollingTransactionSubscriber(provider, event); }
assert(false, "unsupported polling event", "UNSUPPORTED_OPERATION", {
operation: "getPollingSubscriber", info: { event }
});
}
// @TODO: refactor this
/**
* A **PollingBlockSubscriber** polls at a regular interval for a change
* in the block number.
*
* @_docloc: api/providers/abstract-provider
*/
export class PollingBlockSubscriber implements Subscriber {
#provider: AbstractProvider;
#poller: null | number;
#interval: number;
// The most recent block we have scanned for events. The value -2
// 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;
this.#interval = 4000;
this.#blockNumber = -2;
}
/**
* The polling interval.
*/
get pollingInterval(): number { return this.#interval; }
set pollingInterval(value: number) { this.#interval = value; }
async #poll(): Promise<void> {
try {
const blockNumber = await this.#provider.getBlockNumber();
// Bootstrap poll to setup our initial block number
if (this.#blockNumber === -2) {
this.#blockNumber = blockNumber;
return;
}
// @TODO: Put a cap on the maximum number of events per loop?
if (blockNumber !== this.#blockNumber) {
for (let b = this.#blockNumber + 1; b <= blockNumber; b++) {
// We have been stopped
if (this.#poller == null) { return; }
await this.#provider.emit("block", b);
}
this.#blockNumber = blockNumber;
}
} catch (error) {
// @TODO: Minor bump, add an "error" event to let subscribers
// know things went awry.
//console.log(error);
}
// We have been stopped
if (this.#poller == null) { return; }
this.#poller = this.#provider._setTimeout(this.#poll.bind(this), this.#interval);
}
start(): void {
if (this.#poller) { return; }
this.#poller = this.#provider._setTimeout(this.#poll.bind(this), this.#interval);
this.#poll();
}
stop(): void {
if (!this.#poller) { return; }
this.#provider._clearTimeout(this.#poller);
this.#poller = null;
}
pause(dropWhilePaused?: boolean): void {
this.stop();
if (dropWhilePaused) { this.#blockNumber = -2; }
}
resume(): void {
this.start();
}
}
/**
* An **OnBlockSubscriber** can be sub-classed, with a [[_poll]]
* implmentation which will be called on every new block.
*
* @_docloc: api/providers/abstract-provider
*/
export class OnBlockSubscriber implements Subscriber {
#provider: AbstractProvider;
#poll: (b: number) => void;
#running: boolean;
/**
* Create a new **OnBlockSubscriber** attached to %%provider%%.
*/
constructor(provider: AbstractProvider) {
this.#provider = provider;
this.#running = false;
this.#poll = (blockNumber: number) => {
this._poll(blockNumber, this.#provider);
}
}
/**
* Called on every new block.
*/
async _poll(blockNumber: number, provider: AbstractProvider): Promise<void> {
throw new Error("sub-classes must override this");
}
start(): void {
if (this.#running) { return; }
this.#running = true;
this.#poll(-2);
this.#provider.on("block", this.#poll);
}
stop(): void {
if (!this.#running) { return; }
this.#running = false;
this.#provider.off("block", this.#poll);
}
pause(dropWhilePaused?: boolean): void { this.stop(); }
resume(): void { this.start(); }
}
export class PollingBlockTagSubscriber extends OnBlockSubscriber {
readonly #tag: string;
#lastBlock: number;
constructor(provider: AbstractProvider, tag: string) {
super(provider);
this.#tag = tag;
this.#lastBlock = -2;
}
pause(dropWhilePaused?: boolean): void {
if (dropWhilePaused) { this.#lastBlock = -2; }
super.pause(dropWhilePaused);
}
async _poll(blockNumber: number, provider: AbstractProvider): Promise<void> {
const block = await provider.getBlock(this.#tag);
if (block == null) { return; }
if (this.#lastBlock === -2) {
this.#lastBlock = block.number;
} else if (block.number > this.#lastBlock) {
provider.emit(this.#tag, block.number);
this.#lastBlock = block.number;
}
}
}
/**
* @_ignore:
*
* @_docloc: api/providers/abstract-provider
*/
export class PollingOrphanSubscriber extends OnBlockSubscriber {
#filter: OrphanFilter;
constructor(provider: AbstractProvider, filter: OrphanFilter) {
super(provider);
this.#filter = copy(filter);
}
async _poll(blockNumber: number, provider: AbstractProvider): Promise<void> {
throw new Error("@TODO");
console.log(this.#filter);
}
}
/**
* 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;
}
async _poll(blockNumber: number, provider: AbstractProvider): Promise<void> {
const tx = await provider.getTransactionReceipt(this.#hash);
if (tx) { provider.emit(this.#hash, tx); }
}
}
/**
* A **PollingEventSubscriber** will poll for a given filter for its logs.
*
* @_docloc: api/providers/abstract-provider
*/
export class PollingEventSubscriber implements Subscriber {
#provider: AbstractProvider;
#filter: EventFilter;
#poller: (b: number) => void;
#running: boolean;
// The most recent block we have scanned for events. The value -2
// 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);
this.#poller = this.#poll.bind(this);
this.#running = false;
this.#blockNumber = -2;
}
async #poll(blockNumber: number): Promise<void> {
// The initial block hasn't been determined yet
if (this.#blockNumber === -2) { return; }
const filter = copy(this.#filter);
filter.fromBlock = this.#blockNumber + 1;
filter.toBlock = blockNumber;
const logs = await this.#provider.getLogs(filter);
// No logs could just mean the node has not indexed them yet,
// so we keep a sliding window of 60 blocks to keep scanning
if (logs.length === 0) {
if (this.#blockNumber < blockNumber - 60) {
this.#blockNumber = blockNumber - 60;
}
return;
}
for (const log of logs) {
this.#provider.emit(this.#filter, log);
// Only advance the block number when logs were found to
// account for networks (like BNB and Polygon) which may
// sacrifice event consistency for block event speed
this.#blockNumber = log.blockNumber;
}
}
start(): void {
if (this.#running) { return; }
this.#running = true;
if (this.#blockNumber === -2) {
this.#provider.getBlockNumber().then((blockNumber) => {
this.#blockNumber = blockNumber;
});
}
this.#provider.on("block", this.#poller);
}
stop(): void {
if (!this.#running) { return; }
this.#running = false;
this.#provider.off("block", this.#poller);
}
pause(dropWhilePaused?: boolean): void {
this.stop();
if (dropWhilePaused) { this.#blockNumber = -2; }
}
resume(): void {
this.start();
}
}

11
dev/env/node_modules/ethers/src.ts/providers/ws-browser.ts generated vendored Executable file
View File

@@ -0,0 +1,11 @@
function getGlobal(): any {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
const _WebSocket = getGlobal().WebSocket;
export { _WebSocket as WebSocket };

3
dev/env/node_modules/ethers/src.ts/providers/ws.ts generated vendored Executable file
View File

@@ -0,0 +1,3 @@
export { WebSocket } from "ws";