| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761 | /** *  The available providers should suffice for most developers purposes, *  but the [[AbstractProvider]] class has many features which enable *  sub-classing it for specific purposes. * *  @_section: api/providers/abstract-provider: Subclassing Provider  [abstract-provider] */// @TODO// Event coalescence//   When we register an event with an async value (e.g. address is a Signer//   or ENS name), we need to add it immeidately for the Event API, but also//   need time to resolve the address. Upon resolving the address, we need to//   migrate the listener to the static event. We also need to maintain a map//   of Signer/ENS name to address so we can sync respond to listenerCount.import { getAddress, resolveAddress } from "../address/index.js";import { ZeroAddress } from "../constants/index.js";import { Contract } from "../contract/index.js";import { namehash } from "../hash/index.js";import { Transaction } from "../transaction/index.js";import {    concat, dataLength, dataSlice, hexlify, isHexString,    getBigInt, getBytes, getNumber,    isCallException, isError, makeError, assert, assertArgument,    FetchRequest,    toBeArray, toQuantity,    defineProperties, EventPayload, resolveProperties,    toUtf8String} from "../utils/index.js";import { EnsResolver } from "./ens-resolver.js";import {    formatBlock, formatLog, formatTransactionReceipt, formatTransactionResponse} from "./format.js";import { Network } from "./network.js";import { copyRequest, Block, FeeData, Log, TransactionReceipt, TransactionResponse } from "./provider.js";import {    PollingBlockSubscriber, PollingBlockTagSubscriber, PollingEventSubscriber,    PollingOrphanSubscriber, PollingTransactionSubscriber} from "./subscriber-polling.js";import type { Addressable, AddressLike } from "../address/index.js";import type { BigNumberish, BytesLike } from "../utils/index.js";import type { FetchResponse, Listener } from "../utils/index.js";import type { Networkish } from "./network.js";import type { FetchUrlFeeDataNetworkPlugin } from "./plugins-network.js";//import type { MaxPriorityFeePlugin } from "./plugins-network.js";import type {    BlockParams, LogParams, TransactionReceiptParams,    TransactionResponseParams} from "./formatting.js";import type {    BlockTag, EventFilter, Filter, FilterByBlockHash, OrphanFilter,    PreparedTransactionRequest, Provider, ProviderEvent,    TransactionRequest} from "./provider.js";type Timer = ReturnType<typeof setTimeout>;// Constantsconst BN_2 = BigInt(2);const MAX_CCIP_REDIRECTS = 10;function isPromise<T = any>(value: any): value is Promise<T> {    return (value && typeof(value.then) === "function");}function getTag(prefix: string, value: any): string {    return prefix + ":" + JSON.stringify(value, (k, v) => {        if (v == null) { return "null"; }        if (typeof(v) === "bigint") { return `bigint:${ v.toString() }`}        if (typeof(v) === "string") { return v.toLowerCase(); }        // Sort object keys        if (typeof(v) === "object" && !Array.isArray(v)) {            const keys = Object.keys(v);            keys.sort();            return keys.reduce((accum, key) => {                accum[key] = v[key];                return accum;            }, <any>{ });        }        return v;    });}/** *  The types of additional event values that can be emitted for the *  ``"debug"`` event. */export type DebugEventAbstractProvider = {    action: "sendCcipReadFetchRequest",    request: FetchRequest    index: number    urls: Array<string>} | {    action: "receiveCcipReadFetchResult",    request: FetchRequest,    result: any} | {    action: "receiveCcipReadFetchError",    request: FetchRequest,    result: any} | {    action: "sendCcipReadCall",    transaction: { to: string, data: string }} | {    action: "receiveCcipReadCallResult",    transaction: { to: string, data: string }    result: string} | {    action: "receiveCcipReadCallError",    transaction: { to: string, data: string }    error: Error};/** *  The value passed to the [[AbstractProvider-_getSubscriber]] method. * *  Only developers sub-classing [[AbstractProvider[[ will care about this, *  if they are modifying a low-level feature of how subscriptions operate. */export type Subscription = {    type: "block" | "close" | "debug" | "error" | "finalized" | "network" | "pending" | "safe",    tag: string} | {    type: "transaction",    tag: string,    hash: string} | {    type: "event",    tag: string,    filter: EventFilter} | {    type: "orphan",    tag: string,    filter: OrphanFilter};/** *  A **Subscriber** manages a subscription. * *  Only developers sub-classing [[AbstractProvider[[ will care about this, *  if they are modifying a low-level feature of how subscriptions operate. */export interface Subscriber {    /**     *  Called initially when a subscriber is added the first time.     */    start(): void;    /**     *  Called when there are no more subscribers to the event.     */    stop(): void;    /**     *  Called when the subscription should pause.     *     *  If %%dropWhilePaused%%, events that occur while paused should not     *  be emitted [[resume]].     */    pause(dropWhilePaused?: boolean): void;    /**     *  Resume a paused subscriber.     */    resume(): void;    /**     *  The frequency (in ms) to poll for events, if polling is used by     *  the subscriber.     *     *  For non-polling subscribers, this must return ``undefined``.     */    pollingInterval?: number;}/** *  An **UnmanagedSubscriber** is useful for events which do not require *  any additional management, such as ``"debug"`` which only requires *  emit in synchronous event loop triggered calls. */export class UnmanagedSubscriber implements Subscriber {    /**     *  The name fof the event.     */    name!: string;    /**     *  Create a new UnmanagedSubscriber with %%name%%.     */    constructor(name: string) { defineProperties<UnmanagedSubscriber>(this, { name }); }    start(): void { }    stop(): void { }    pause(dropWhilePaused?: boolean): void { }    resume(): void { }}type Sub = {    tag: string;    nameMap: Map<string, string>    addressableMap: WeakMap<Addressable, string>;    listeners: Array<{ listener: Listener, once: boolean }>;    // @TODO: get rid of this, as it is (and has to be)    // tracked in subscriber    started: boolean;    subscriber: Subscriber;};function copy<T = any>(value: T): T {    return JSON.parse(JSON.stringify(value));}function concisify(items: Array<string>): Array<string> {    items = Array.from((new Set(items)).values())    items.sort();    return items;}async function getSubscription(_event: ProviderEvent, provider: AbstractProvider): Promise<Subscription> {    if (_event == null) { throw new Error("invalid event"); }    // Normalize topic array info an EventFilter    if (Array.isArray(_event)) { _event = { topics: _event }; }    if (typeof(_event) === "string") {        switch (_event) {            case "block":            case "debug":            case "error":            case "finalized":            case "network":            case "pending":            case "safe": {                return { type: _event, tag: _event };            }        }    }    if (isHexString(_event, 32)) {        const hash = _event.toLowerCase();        return { type: "transaction", tag: getTag("tx", { hash }), hash };    }    if ((<any>_event).orphan) {        const event = <OrphanFilter>_event;        // @TODO: Should lowercase and whatnot things here instead of copy...        return { type: "orphan", tag: getTag("orphan", event), filter: copy(event) };    }    if (((<any>_event).address || (<any>_event).topics)) {        const event = <EventFilter>_event;        const filter: any = {            topics: ((event.topics || []).map((t) => {                if (t == null) { return null; }                if (Array.isArray(t)) {                    return concisify(t.map((t) => t.toLowerCase()));                }                return t.toLowerCase();            }))        };        if (event.address) {            const addresses: Array<string> = [ ];            const promises: Array<Promise<void>> = [ ];            const addAddress = (addr: AddressLike) => {                if (isHexString(addr)) {                    addresses.push(addr);                } else {                    promises.push((async () => {                        addresses.push(await resolveAddress(addr, provider));                    })());                }            }            if (Array.isArray(event.address)) {                event.address.forEach(addAddress);            } else {                addAddress(event.address);            }            if (promises.length) { await Promise.all(promises); }            filter.address = concisify(addresses.map((a) => a.toLowerCase()));        }        return { filter, tag: getTag("event", filter), type: "event" };    }    assertArgument(false, "unknown ProviderEvent", "event", _event);}function getTime(): number { return (new Date()).getTime(); }/** *  An **AbstractPlugin** is used to provide additional internal services *  to an [[AbstractProvider]] without adding backwards-incompatible changes *  to method signatures or other internal and complex logic. */export interface AbstractProviderPlugin {    /**     *  The reverse domain notation of the plugin.     */    readonly name: string;    /**     *  Creates a new instance of the plugin, connected to %%provider%%.     */    connect(provider: AbstractProvider): AbstractProviderPlugin;}/** *  A normalized filter used for [[PerformActionRequest]] objects. */export type PerformActionFilter = {    address?: string | Array<string>;    topics?: Array<null | string | Array<string>>;    fromBlock?: BlockTag;    toBlock?: BlockTag;} | {    address?: string | Array<string>;    topics?: Array<null | string | Array<string>>;    blockHash?: string;};/** *  A normalized transactions used for [[PerformActionRequest]] objects. */export interface PerformActionTransaction extends PreparedTransactionRequest {    /**     *  The ``to`` address of the transaction.     */    to?: string;    /**     *  The sender of the transaction.     */    from?: string;}/** *  The [[AbstractProvider]] methods will normalize all values and pass this *  type to [[AbstractProvider-_perform]]. */export type PerformActionRequest = {    method: "broadcastTransaction",    signedTransaction: string} | {    method: "call",    transaction: PerformActionTransaction, blockTag: BlockTag} | {    method: "chainId"} | {    method: "estimateGas",    transaction: PerformActionTransaction} | {    method: "getBalance",    address: string, blockTag: BlockTag} | {    method: "getBlock",    blockTag: BlockTag, includeTransactions: boolean} | {    method: "getBlock",    blockHash: string, includeTransactions: boolean} | {    method: "getBlockNumber"} | {    method: "getCode",    address: string, blockTag: BlockTag} | {    method: "getGasPrice"} | {    method: "getLogs",    filter: PerformActionFilter} | {    method: "getPriorityFee"} | {    method: "getStorage",    address: string, position: bigint, blockTag: BlockTag} | {    method: "getTransaction",    hash: string} | {    method: "getTransactionCount",    address: string, blockTag: BlockTag} | {    method: "getTransactionReceipt",    hash: string} | {    method: "getTransactionResult",    hash: string};type _PerformAccountRequest = {    method: "getBalance" | "getTransactionCount" | "getCode"} | {    method: "getStorage", position: bigint}/** *  Options for configuring some internal aspects of an [[AbstractProvider]]. * *  **``cacheTimeout``** - how long to cache a low-level ``_perform`` *  for, based on input parameters. This reduces the number of calls *  to getChainId and getBlockNumber, but may break test chains which *  can perform operations (internally) synchronously. Use ``-1`` to *  disable, ``0`` will only buffer within the same event loop and *  any other value is in ms. (default: ``250``) */export type AbstractProviderOptions = {    cacheTimeout?: number;    pollingInterval?: number;};const defaultOptions = {    cacheTimeout: 250,    pollingInterval: 4000};type CcipArgs = {    sender: string;    urls: Array<string>;    calldata: string;    selector: string;    extraData: string;    errorArgs: Array<any>};/** *  An **AbstractProvider** provides a base class for other sub-classes to *  implement the [[Provider]] API by normalizing input arguments and *  formatting output results as well as tracking events for consistent *  behaviour on an eventually-consistent network. */export class AbstractProvider implements Provider {    #subs: Map<string, Sub>;    #plugins: Map<string, AbstractProviderPlugin>;    // null=unpaused, true=paused+dropWhilePaused, false=paused    #pausedState: null | boolean;    #destroyed: boolean;    #networkPromise: null | Promise<Network>;    readonly #anyNetwork: boolean;    #performCache: Map<string, Promise<any>>;    // The most recent block number if running an event or -1 if no "block" event    #lastBlockNumber: number;    #nextTimer: number;    #timers: Map<number, { timer: null | Timer, func: () => void, time: number }>;    #disableCcipRead: boolean;    #options: Required<AbstractProviderOptions>;    /**     *  Create a new **AbstractProvider** connected to %%network%%, or     *  use the various network detection capabilities to discover the     *  [[Network]] if necessary.     */    constructor(_network?: "any" | Networkish, options?: AbstractProviderOptions) {        this.#options = Object.assign({ }, defaultOptions, options || { });        if (_network === "any") {            this.#anyNetwork = true;            this.#networkPromise = null;        } else if (_network) {            const network = Network.from(_network);            this.#anyNetwork = false;            this.#networkPromise = Promise.resolve(network);            setTimeout(() => { this.emit("network", network, null); }, 0);        } else {            this.#anyNetwork = false;            this.#networkPromise = null;        }        this.#lastBlockNumber = -1;        this.#performCache = new Map();        this.#subs = new Map();        this.#plugins = new Map();        this.#pausedState = null;        this.#destroyed = false;        this.#nextTimer = 1;        this.#timers = new Map();        this.#disableCcipRead = false;    }    get pollingInterval(): number { return this.#options.pollingInterval; }    /**     *  Returns ``this``, to allow an **AbstractProvider** to implement     *  the [[ContractRunner]] interface.     */    get provider(): this { return this; }    /**     *  Returns all the registered plug-ins.     */    get plugins(): Array<AbstractProviderPlugin> {        return Array.from(this.#plugins.values());    }    /**     *  Attach a new plug-in.     */    attachPlugin(plugin: AbstractProviderPlugin): this {        if (this.#plugins.get(plugin.name)) {            throw new Error(`cannot replace existing plugin: ${ plugin.name } `);        }        this.#plugins.set(plugin.name,  plugin.connect(this));        return this;    }    /**     *  Get a plugin by name.     */    getPlugin<T extends AbstractProviderPlugin = AbstractProviderPlugin>(name: string): null | T {        return <T>(this.#plugins.get(name)) || null;    }    /**     *  Prevent any CCIP-read operation, regardless of whether requested     *  in a [[call]] using ``enableCcipRead``.     */    get disableCcipRead(): boolean { return this.#disableCcipRead; }    set disableCcipRead(value: boolean) { this.#disableCcipRead = !!value; }    // Shares multiple identical requests made during the same 250ms    async #perform<T = any>(req: PerformActionRequest): Promise<T> {        const timeout = this.#options.cacheTimeout;        // Caching disabled        if (timeout < 0) { return await this._perform(req); }        // Create a tag        const tag = getTag(req.method, req);        let perform = this.#performCache.get(tag);        if (!perform) {            perform = this._perform(req);            this.#performCache.set(tag, perform);            setTimeout(() => {                if (this.#performCache.get(tag) === perform) {                    this.#performCache.delete(tag);                }            }, timeout);        }        return await perform;    }    /**     *  Resolves to the data for executing the CCIP-read operations.     */    async ccipReadFetch(tx: PerformActionTransaction, calldata: string, urls: Array<string>): Promise<null | string> {        if (this.disableCcipRead || urls.length === 0 || tx.to == null) { return null; }        const sender = tx.to.toLowerCase();        const data = calldata.toLowerCase();        const errorMessages: Array<string> = [ ];        for (let i = 0; i < urls.length; i++) {            const url = urls[i];            // URL expansion            const href = url.replace("{sender}", sender).replace("{data}", data);            // If no {data} is present, use POST; otherwise GET            //const json: string | null = (url.indexOf("{data}") >= 0) ? null: JSON.stringify({ data, sender });            //const result = await fetchJson({ url: href, errorPassThrough: true }, json, (value, response) => {            //    value.status = response.statusCode;            //    return value;            //});            const request = new FetchRequest(href);            if (url.indexOf("{data}") === -1) {                request.body = { data, sender };            }            this.emit("debug", { action: "sendCcipReadFetchRequest", request, index: i, urls });            let errorMessage = "unknown error";            // Fetch the resource...            let resp: FetchResponse;            try {                resp = await request.send();            } catch (error: any) {                // ...low-level fetch error (missing host, bad SSL, etc.),                // so try next URL                errorMessages.push(error.message);                this.emit("debug", { action: "receiveCcipReadFetchError", request, result: { error } });                continue;            }            try {                const result = resp.bodyJson;                if (result.data) {                    this.emit("debug", { action: "receiveCcipReadFetchResult", request, result });                    return result.data;                }                if (result.message) { errorMessage = result.message; }                this.emit("debug", { action: "receiveCcipReadFetchError", request, result });            } catch (error) { }            // 4xx indicates the result is not present; stop            assert(resp.statusCode < 400 || resp.statusCode >= 500, `response not found during CCIP fetch: ${ errorMessage }`,                "OFFCHAIN_FAULT", { reason: "404_MISSING_RESOURCE", transaction: tx, info: { url, errorMessage } });            // 5xx indicates server issue; try the next url            errorMessages.push(errorMessage);        }        assert(false, `error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, "OFFCHAIN_FAULT", {            reason: "500_SERVER_ERROR",            transaction: tx, info: { urls, errorMessages }        });    }    /**     *  Provides the opportunity for a sub-class to wrap a block before     *  returning it, to add additional properties or an alternate     *  sub-class of [[Block]].     */    _wrapBlock(value: BlockParams, network: Network): Block {        return new Block(formatBlock(value), this);    }    /**     *  Provides the opportunity for a sub-class to wrap a log before     *  returning it, to add additional properties or an alternate     *  sub-class of [[Log]].     */    _wrapLog(value: LogParams, network: Network): Log {        return new Log(formatLog(value), this);    }    /**     *  Provides the opportunity for a sub-class to wrap a transaction     *  receipt before returning it, to add additional properties or an     *  alternate sub-class of [[TransactionReceipt]].     */    _wrapTransactionReceipt(value: TransactionReceiptParams, network: Network): TransactionReceipt {        return new TransactionReceipt(formatTransactionReceipt(value), this);    }    /**     *  Provides the opportunity for a sub-class to wrap a transaction     *  response before returning it, to add additional properties or an     *  alternate sub-class of [[TransactionResponse]].     */    _wrapTransactionResponse(tx: TransactionResponseParams, network: Network): TransactionResponse {        return new TransactionResponse(formatTransactionResponse(tx), this);    }    /**     *  Resolves to the Network, forcing a network detection using whatever     *  technique the sub-class requires.     *     *  Sub-classes **must** override this.     */    _detectNetwork(): Promise<Network> {        assert(false, "sub-classes must implement this", "UNSUPPORTED_OPERATION", {            operation: "_detectNetwork"        });    }    /**     *  Sub-classes should use this to perform all built-in operations. All     *  methods sanitizes and normalizes the values passed into this.     *     *  Sub-classes **must** override this.     */    async _perform<T = any>(req: PerformActionRequest): Promise<T> {        assert(false, `unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", {            operation: req.method,            info: req        });    }    // State    async getBlockNumber(): Promise<number> {        const blockNumber = getNumber(await this.#perform({ method: "getBlockNumber" }), "%response");        if (this.#lastBlockNumber >= 0) { this.#lastBlockNumber = blockNumber; }        return blockNumber;    }    /**     *  Returns or resolves to the address for %%address%%, resolving ENS     *  names and [[Addressable]] objects and returning if already an     *  address.     */    _getAddress(address: AddressLike): string | Promise<string> {        return resolveAddress(address, this);    }    /**     *  Returns or resolves to a valid block tag for %%blockTag%%, resolving     *  negative values and returning if already a valid block tag.     */    _getBlockTag(blockTag?: BlockTag): string | Promise<string> {        if (blockTag == null) { return "latest"; }        switch (blockTag) {            case "earliest":                return "0x0";            case "finalized":            case "latest":            case "pending":            case "safe":                return blockTag;        }        if (isHexString(blockTag)) {            if (isHexString(blockTag, 32)) { return blockTag; }            return toQuantity(blockTag);        }        if (typeof(blockTag) === "bigint") {            blockTag = getNumber(blockTag, "blockTag");        }        if (typeof(blockTag) === "number") {            if (blockTag >= 0) { return toQuantity(blockTag); }            if (this.#lastBlockNumber >= 0) { return toQuantity(this.#lastBlockNumber + blockTag); }            return this.getBlockNumber().then((b) => toQuantity(b + <number>blockTag));        }        assertArgument(false, "invalid blockTag", "blockTag", blockTag);    }    /**     *  Returns or resolves to a filter for %%filter%%, resolving any ENS     *  names or [[Addressable]] object and returning if already a valid     *  filter.     */    _getFilter(filter: Filter | FilterByBlockHash): PerformActionFilter | Promise<PerformActionFilter> {        // Create a canonical representation of the topics        const topics = (filter.topics || [ ]).map((t) => {            if (t == null) { return null; }            if (Array.isArray(t)) {                return concisify(t.map((t) => t.toLowerCase()));            }            return t.toLowerCase();        });        const blockHash = ("blockHash" in filter) ? filter.blockHash: undefined;        const resolve = (_address: Array<string>, fromBlock?: string, toBlock?: string) => {            let address: undefined | string | Array<string> = undefined;            switch (_address.length) {                case 0: break;                case 1:                    address = _address[0];                    break;                default:                    _address.sort();                    address = _address;            }            if (blockHash) {                if (fromBlock != null || toBlock != null) {                    throw new Error("invalid filter");                }            }            const filter = <any>{ };            if (address) { filter.address = address; }            if (topics.length) { filter.topics = topics; }            if (fromBlock) { filter.fromBlock = fromBlock; }            if (toBlock) { filter.toBlock = toBlock; }            if (blockHash) { filter.blockHash = blockHash; }            return filter;        };        // Addresses could be async (ENS names or Addressables)        let address: Array<string | Promise<string>> = [ ];        if (filter.address) {            if (Array.isArray(filter.address)) {                for (const addr of filter.address) { address.push(this._getAddress(addr)); }            } else {                address.push(this._getAddress(filter.address));            }        }        let fromBlock: undefined | string | Promise<string> = undefined;        if ("fromBlock" in filter) { fromBlock = this._getBlockTag(filter.fromBlock); }        let toBlock: undefined | string | Promise<string> = undefined;        if ("toBlock" in filter) { toBlock = this._getBlockTag(filter.toBlock); }        if (address.filter((a) => (typeof(a) !== "string")).length ||            (fromBlock != null && typeof(fromBlock) !== "string") ||            (toBlock != null && typeof(toBlock) !== "string")) {            return Promise.all([ Promise.all(address), fromBlock, toBlock ]).then((result) => {                return resolve(result[0], result[1], result[2]);            });        }        return resolve(<Array<string>>address, fromBlock, toBlock);    }    /**     *  Returns or resolves to a transaction for %%request%%, resolving     *  any ENS names or [[Addressable]] and returning if already a valid     *  transaction.     */    _getTransactionRequest(_request: TransactionRequest): PerformActionTransaction | Promise<PerformActionTransaction> {        const request = <PerformActionTransaction>copyRequest(_request);        const promises: Array<Promise<void>> = [ ];        [ "to", "from" ].forEach((key) => {            if ((<any>request)[key] == null) { return; }            const addr = resolveAddress((<any>request)[key], this);            if (isPromise(addr)) {                promises.push((async function() { (<any>request)[key] = await addr; })());            } else {                (<any>request)[key] = addr;            }        });        if (request.blockTag != null) {            const blockTag = this._getBlockTag(request.blockTag);            if (isPromise(blockTag)) {                promises.push((async function() { request.blockTag = await blockTag; })());            } else {                request.blockTag = blockTag;            }        }        if (promises.length) {            return (async function() {                await Promise.all(promises);                return request;            })();        }        return request;    }    async getNetwork(): Promise<Network> {        // No explicit network was set and this is our first time        if (this.#networkPromise == null) {            // Detect the current network (shared with all calls)            const detectNetwork = (async () => {                try {                    const network = await this._detectNetwork();                    this.emit("network", network, null);                    return network;                } catch (error) {                    if (this.#networkPromise === detectNetwork!) {                        this.#networkPromise = null;                    }                    throw error;                }            })();            this.#networkPromise = detectNetwork;            return (await detectNetwork).clone();        }        const networkPromise = this.#networkPromise;        const [ expected, actual ] = await Promise.all([            networkPromise,          // Possibly an explicit Network            this._detectNetwork()    // The actual connected network        ]);        if (expected.chainId !== actual.chainId) {            if (this.#anyNetwork) {                // The "any" network can change, so notify listeners                this.emit("network", actual, expected);                // Update the network if something else hasn't already changed it                if (this.#networkPromise === networkPromise) {                    this.#networkPromise = Promise.resolve(actual);                }            } else {                // Otherwise, we do not allow changes to the underlying network                assert(false, `network changed: ${ expected.chainId } => ${ actual.chainId } `, "NETWORK_ERROR", {                    event: "changed"                });            }        }        return expected.clone();    }    async getFeeData(): Promise<FeeData> {        const network = await this.getNetwork();        const getFeeDataFunc = async () => {            const { _block, gasPrice, priorityFee } = await resolveProperties({                _block: this.#getBlock("latest", false),                gasPrice: ((async () => {                    try {                        const value = await this.#perform({ method: "getGasPrice" });                        return getBigInt(value, "%response");                    } catch (error) { }                    return null                })()),                priorityFee: ((async () => {                    try {                        const value = await this.#perform({ method: "getPriorityFee" });                        return getBigInt(value, "%response");                    } catch (error) { }                    return null;                })())            });            let maxFeePerGas: null | bigint = null;            let maxPriorityFeePerGas: null | bigint = null;            // These are the recommended EIP-1559 heuristics for fee data            const block = this._wrapBlock(_block, network);            if (block && block.baseFeePerGas) {                maxPriorityFeePerGas = (priorityFee != null) ? priorityFee: BigInt("1000000000");                maxFeePerGas = (block.baseFeePerGas * BN_2) + maxPriorityFeePerGas;            }            return new FeeData(gasPrice, maxFeePerGas, maxPriorityFeePerGas);        };        // Check for a FeeDataNetWorkPlugin        const plugin = <FetchUrlFeeDataNetworkPlugin>network.getPlugin("org.ethers.plugins.network.FetchUrlFeeDataPlugin");        if (plugin) {            const req = new FetchRequest(plugin.url);            const feeData = await plugin.processFunc(getFeeDataFunc, this, req);            return new FeeData(feeData.gasPrice, feeData.maxFeePerGas, feeData.maxPriorityFeePerGas);        }        return await getFeeDataFunc();    }    async estimateGas(_tx: TransactionRequest): Promise<bigint> {        let tx = this._getTransactionRequest(_tx);        if (isPromise(tx)) { tx = await tx; }        return getBigInt(await this.#perform({            method: "estimateGas", transaction: tx        }), "%response");    }    async #call(tx: PerformActionTransaction, blockTag: string, attempt: number): Promise<string> {        assert (attempt < MAX_CCIP_REDIRECTS, "CCIP read exceeded maximum redirections", "OFFCHAIN_FAULT", {             reason: "TOO_MANY_REDIRECTS",             transaction: Object.assign({ }, tx, { blockTag, enableCcipRead: true })         });         // This came in as a PerformActionTransaction, so to/from are safe; we can cast         const transaction = <PerformActionTransaction>copyRequest(tx);         try {             return hexlify(await this._perform({ method: "call", transaction, blockTag }));         } catch (error: any) {             // CCIP Read OffchainLookup             if (!this.disableCcipRead && isCallException(error) && error.data && attempt >= 0 && blockTag === "latest" && transaction.to != null && dataSlice(error.data, 0, 4) === "0x556f1830") {                 const data = error.data;                 const txSender = await resolveAddress(transaction.to, this);                 // Parse the CCIP Read Arguments                 let ccipArgs: CcipArgs;                 try {                     ccipArgs = parseOffchainLookup(dataSlice(error.data, 4));                 } catch (error: any) {                     assert(false, error.message, "OFFCHAIN_FAULT", {                         reason: "BAD_DATA", transaction, info: { data } });                 }                 // Check the sender of the OffchainLookup matches the transaction                 assert(ccipArgs.sender.toLowerCase() === txSender.toLowerCase(),                     "CCIP Read sender mismatch", "CALL_EXCEPTION", {                         action: "call",                         data,                         reason: "OffchainLookup",                         transaction: <any>transaction, // @TODO: populate data?                         invocation: null,                         revert: {                             signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",                             name: "OffchainLookup",                             args: ccipArgs.errorArgs                         }                     });                 const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls);                 assert(ccipResult != null, "CCIP Read failed to fetch data", "OFFCHAIN_FAULT", {                     reason: "FETCH_FAILED", transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs } });                 const tx = {                     to: txSender,                     data: concat([ ccipArgs.selector, encodeBytes([ ccipResult, ccipArgs.extraData ]) ])                 };                 this.emit("debug", { action: "sendCcipReadCall", transaction: tx });                 try {                     const result = await this.#call(tx, blockTag, attempt + 1);                     this.emit("debug", { action: "receiveCcipReadCallResult", transaction: Object.assign({ }, tx), result });                     return result;                 } catch (error) {                     this.emit("debug", { action: "receiveCcipReadCallError", transaction: Object.assign({ }, tx), error });                     throw error;                 }             }             throw error;         }    }    async #checkNetwork<T>(promise: Promise<T>): Promise<T> {        const { value } = await resolveProperties({            network: this.getNetwork(),            value: promise        });        return value;    }    async call(_tx: TransactionRequest): Promise<string> {        const { tx, blockTag } = await resolveProperties({            tx: this._getTransactionRequest(_tx),            blockTag: this._getBlockTag(_tx.blockTag)        });        return await this.#checkNetwork(this.#call(tx, blockTag, _tx.enableCcipRead ? 0: -1));    }    // Account    async #getAccountValue(request: _PerformAccountRequest, _address: AddressLike, _blockTag?: BlockTag): Promise<any> {        let address: string | Promise<string> = this._getAddress(_address);        let blockTag: string | Promise<string> = this._getBlockTag(_blockTag);        if (typeof(address) !== "string" || typeof(blockTag) !== "string") {            [ address, blockTag ] = await Promise.all([ address, blockTag ]);        }        return await this.#checkNetwork(this.#perform(Object.assign(request, { address, blockTag })));    }    async getBalance(address: AddressLike, blockTag?: BlockTag): Promise<bigint> {        return getBigInt(await this.#getAccountValue({ method: "getBalance" }, address, blockTag), "%response");    }    async getTransactionCount(address: AddressLike, blockTag?: BlockTag): Promise<number> {        return getNumber(await this.#getAccountValue({ method: "getTransactionCount" }, address, blockTag), "%response");    }    async getCode(address: AddressLike, blockTag?: BlockTag): Promise<string> {        return hexlify(await this.#getAccountValue({ method: "getCode" }, address, blockTag));    }    async getStorage(address: AddressLike, _position: BigNumberish, blockTag?: BlockTag): Promise<string> {        const position = getBigInt(_position, "position");        return hexlify(await this.#getAccountValue({ method: "getStorage", position }, address, blockTag));    }    // Write    async broadcastTransaction(signedTx: string): Promise<TransactionResponse> {        const { blockNumber, hash, network } = await resolveProperties({             blockNumber: this.getBlockNumber(),             hash: this._perform({                 method: "broadcastTransaction",                 signedTransaction: signedTx             }),             network: this.getNetwork()        });        const tx = Transaction.from(signedTx);        if (tx.hash !== hash) {            throw new Error("@TODO: the returned hash did not match");        }        return this._wrapTransactionResponse(<any>tx, network).replaceableTransaction(blockNumber);    }    async #getBlock(block: BlockTag | string, includeTransactions: boolean): Promise<any> {        // @TODO: Add CustomBlockPlugin check        if (isHexString(block, 32)) {            return await this.#perform({                method: "getBlock", blockHash: block, includeTransactions            });        }        let blockTag = this._getBlockTag(block);        if (typeof(blockTag) !== "string") { blockTag = await blockTag; }        return await this.#perform({            method: "getBlock", blockTag, includeTransactions        });    }    // Queries    async getBlock(block: BlockTag | string, prefetchTxs?: boolean): Promise<null | Block> {        const { network, params } = await resolveProperties({            network: this.getNetwork(),            params: this.#getBlock(block, !!prefetchTxs)        });        if (params == null) { return null; }        return this._wrapBlock(params, network);    }    async getTransaction(hash: string): Promise<null | TransactionResponse> {        const { network, params } = await resolveProperties({            network: this.getNetwork(),            params: this.#perform({ method: "getTransaction", hash })        });        if (params == null) { return null; }        return this._wrapTransactionResponse(params, network);    }    async getTransactionReceipt(hash: string): Promise<null | TransactionReceipt> {        const { network, params } = await resolveProperties({            network: this.getNetwork(),            params: this.#perform({ method: "getTransactionReceipt", hash })        });        if (params == null) { return null; }        // Some backends did not backfill the effectiveGasPrice into old transactions        // in the receipt, so we look it up manually and inject it.        if (params.gasPrice == null && params.effectiveGasPrice == null) {            const tx = await this.#perform({ method: "getTransaction", hash });            if (tx == null) { throw new Error("report this; could not find tx or effectiveGasPrice"); }            params.effectiveGasPrice = tx.gasPrice;        }        return this._wrapTransactionReceipt(params, network);    }    async getTransactionResult(hash: string): Promise<null | string> {        const { result } = await resolveProperties({            network: this.getNetwork(),            result: this.#perform({ method: "getTransactionResult", hash })        });        if (result == null) { return null; }        return hexlify(result);    }    // Bloom-filter Queries    async getLogs(_filter: Filter | FilterByBlockHash): Promise<Array<Log>> {        let filter = this._getFilter(_filter);        if (isPromise(filter)) { filter = await filter; }        const { network, params } = await resolveProperties({            network: this.getNetwork(),            params: this.#perform<Array<LogParams>>({ method: "getLogs", filter })        });        return params.map((p) => this._wrapLog(p, network));    }    // ENS    _getProvider(chainId: number): AbstractProvider {        assert(false, "provider cannot connect to target network", "UNSUPPORTED_OPERATION", {            operation: "_getProvider()"        });    }    async getResolver(name: string): Promise<null | EnsResolver> {        return await EnsResolver.fromName(this, name);    }    async getAvatar(name: string): Promise<null | string> {        const resolver = await this.getResolver(name);        if (resolver) { return await resolver.getAvatar(); }        return null;    }    async resolveName(name: string): Promise<null | string>{        const resolver = await this.getResolver(name);        if (resolver) { return await resolver.getAddress(); }        return null;    }    async lookupAddress(address: string): Promise<null | string> {        address = getAddress(address);        const node = namehash(address.substring(2).toLowerCase() + ".addr.reverse");        try {            const ensAddr = await EnsResolver.getEnsAddress(this);            const ensContract = new Contract(ensAddr, [                "function resolver(bytes32) view returns (address)"            ], this);            const resolver = await ensContract.resolver(node);            if (resolver == null || resolver === ZeroAddress) { return null; }            const resolverContract = new Contract(resolver, [                "function name(bytes32) view returns (string)"            ], this);            const name = await resolverContract.name(node);            // Failed forward resolution            const check = await this.resolveName(name);            if (check !== address) { return null; }            return name;        } catch (error) {            // No data was returned from the resolver            if (isError(error, "BAD_DATA") && error.value === "0x") {                return null;            }            // Something reerted            if (isError(error, "CALL_EXCEPTION")) { return null; }            throw error;        }        return null;    }    async waitForTransaction(hash: string, _confirms?: null | number, timeout?: null | number): Promise<null | TransactionReceipt> {        const confirms = (_confirms != null) ? _confirms: 1;        if (confirms === 0) { return this.getTransactionReceipt(hash); }        return new Promise(async (resolve, reject) => {            let timer: null | Timer = null;            const listener = (async (blockNumber: number) => {                try {                    const receipt = await this.getTransactionReceipt(hash);                    if (receipt != null) {                        if (blockNumber - receipt.blockNumber + 1 >= confirms) {                            resolve(receipt);                            //this.off("block", listener);                            if (timer) {                                clearTimeout(timer);                                timer = null;                            }                            return;                        }                    }                } catch (error) {                    console.log("EEE", error);                }                this.once("block", listener);            });            if (timeout != null) {                timer = setTimeout(() => {                    if (timer == null) { return; }                    timer = null;                    this.off("block", listener);                    reject(makeError("timeout", "TIMEOUT", { reason: "timeout" }));                }, timeout);            }            listener(await this.getBlockNumber());        });    }    async waitForBlock(blockTag?: BlockTag): Promise<Block> {        assert(false, "not implemented yet", "NOT_IMPLEMENTED", {            operation: "waitForBlock"        });    }    /**     *  Clear a timer created using the [[_setTimeout]] method.     */    _clearTimeout(timerId: number): void {        const timer = this.#timers.get(timerId);        if (!timer) { return; }        if (timer.timer) { clearTimeout(timer.timer); }        this.#timers.delete(timerId);    }    /**     *  Create a timer that will execute %%func%% after at least %%timeout%%     *  (in ms). If %%timeout%% is unspecified, then %%func%% will execute     *  in the next event loop.     *     *  [Pausing](AbstractProvider-paused) the provider will pause any     *  associated timers.     */    _setTimeout(_func: () => void, timeout?: number): number {        if (timeout == null) { timeout = 0; }        const timerId = this.#nextTimer++;        const func = () => {            this.#timers.delete(timerId);            _func();        };        if (this.paused) {            this.#timers.set(timerId, { timer: null, func, time: timeout });        } else {            const timer = setTimeout(func, timeout);            this.#timers.set(timerId, { timer, func, time: getTime() });        }        return timerId;    }    /**     *  Perform %%func%% on each subscriber.     */    _forEachSubscriber(func: (s: Subscriber) => void): void {        for (const sub of this.#subs.values()) {            func(sub.subscriber);        }    }    /**     *  Sub-classes may override this to customize subscription     *  implementations.     */    _getSubscriber(sub: Subscription): Subscriber {        switch (sub.type) {            case "debug":            case "error":            case "network":                return new UnmanagedSubscriber(sub.type);            case "block": {                const subscriber = new PollingBlockSubscriber(this);                subscriber.pollingInterval = this.pollingInterval;                return subscriber;            }            case "safe": case "finalized":                return new PollingBlockTagSubscriber(this, sub.type);            case "event":                return new PollingEventSubscriber(this, sub.filter);            case "transaction":                return new PollingTransactionSubscriber(this, sub.hash);            case "orphan":                return new PollingOrphanSubscriber(this, sub.filter);        }        throw new Error(`unsupported event: ${ sub.type }`);    }    /**     *  If a [[Subscriber]] fails and needs to replace itself, this     *  method may be used.     *     *  For example, this is used for providers when using the     *  ``eth_getFilterChanges`` method, which can return null if state     *  filters are not supported by the backend, allowing the Subscriber     *  to swap in a [[PollingEventSubscriber]].     */    _recoverSubscriber(oldSub: Subscriber, newSub: Subscriber): void {        for (const sub of this.#subs.values()) {            if (sub.subscriber === oldSub) {                if (sub.started) { sub.subscriber.stop(); }                sub.subscriber = newSub;                if (sub.started) { newSub.start(); }                if (this.#pausedState != null) { newSub.pause(this.#pausedState); }                break;            }        }    }    async #hasSub(event: ProviderEvent, emitArgs?: Array<any>): Promise<null | Sub> {        let sub = await getSubscription(event, this);        // This is a log that is removing an existing log; we actually want        // to emit an orphan event for the removed log        if (sub.type === "event" && emitArgs && emitArgs.length > 0 && emitArgs[0].removed === true) {            sub = await getSubscription({ orphan: "drop-log", log: emitArgs[0] }, this);        }        return this.#subs.get(sub.tag) || null;    }    async #getSub(event: ProviderEvent): Promise<Sub> {        const subscription = await getSubscription(event, this);        // Prevent tampering with our tag in any subclass' _getSubscriber        const tag = subscription.tag;        let sub = this.#subs.get(tag);        if (!sub) {            const subscriber = this._getSubscriber(subscription);            const addressableMap = new WeakMap();            const nameMap = new Map();            sub = { subscriber, tag, addressableMap, nameMap, started: false, listeners: [ ] };            this.#subs.set(tag, sub);        }        return sub;    }    async on(event: ProviderEvent, listener: Listener): Promise<this> {        const sub = await this.#getSub(event);        sub.listeners.push({ listener, once: false });        if (!sub.started) {            sub.subscriber.start();            sub.started = true;            if (this.#pausedState != null) { sub.subscriber.pause(this.#pausedState); }        }        return this;    }    async once(event: ProviderEvent, listener: Listener): Promise<this> {        const sub = await this.#getSub(event);        sub.listeners.push({ listener, once: true });        if (!sub.started) {            sub.subscriber.start();            sub.started = true;            if (this.#pausedState != null) { sub.subscriber.pause(this.#pausedState); }        }        return this;    }    async emit(event: ProviderEvent, ...args: Array<any>): Promise<boolean> {        const sub = await this.#hasSub(event, args);        // If there is not subscription or if a recent emit removed        // the last of them (which also deleted the sub) do nothing        if (!sub || sub.listeners.length === 0) { return false; };        const count = sub.listeners.length;        sub.listeners = sub.listeners.filter(({ listener, once }) => {            const payload = new EventPayload(this, (once ? null: listener), event);            try {                listener.call(this, ...args, payload);            } catch(error) { }            return !once;        });        if (sub.listeners.length === 0) {            if (sub.started) { sub.subscriber.stop(); }            this.#subs.delete(sub.tag);        }        return (count > 0);    }    async listenerCount(event?: ProviderEvent): Promise<number> {        if (event) {            const sub = await this.#hasSub(event);            if (!sub) { return 0; }            return sub.listeners.length;        }        let total = 0;        for (const { listeners } of this.#subs.values()) {            total += listeners.length;        }        return total;    }    async listeners(event?: ProviderEvent): Promise<Array<Listener>> {        if (event) {            const sub = await this.#hasSub(event);            if (!sub) { return  [ ]; }            return sub.listeners.map(({ listener }) => listener);        }        let result: Array<Listener> = [ ];        for (const { listeners } of this.#subs.values()) {            result = result.concat(listeners.map(({ listener }) => listener));        }        return result;    }    async off(event: ProviderEvent, listener?: Listener): Promise<this> {        const sub = await this.#hasSub(event);        if (!sub) { return this; }        if (listener) {            const index = sub.listeners.map(({ listener }) => listener).indexOf(listener);            if (index >= 0) { sub.listeners.splice(index, 1); }        }        if (!listener || sub.listeners.length === 0) {            if (sub.started) { sub.subscriber.stop(); }            this.#subs.delete(sub.tag);        }        return this;    }    async removeAllListeners(event?: ProviderEvent): Promise<this> {        if (event) {            const { tag, started, subscriber } = await this.#getSub(event);            if (started) { subscriber.stop(); }            this.#subs.delete(tag);        } else {            for (const [ tag, { started, subscriber } ] of this.#subs) {                if (started) { subscriber.stop(); }                this.#subs.delete(tag);            }        }        return this;    }    // Alias for "on"    async addListener(event: ProviderEvent, listener: Listener): Promise<this> {       return await this.on(event, listener);    }    // Alias for "off"    async removeListener(event: ProviderEvent, listener: Listener): Promise<this> {       return this.off(event, listener);    }    /**     *  If this provider has been destroyed using the [[destroy]] method.     *     *  Once destroyed, all resources are reclaimed, internal event loops     *  and timers are cleaned up and no further requests may be sent to     *  the provider.     */    get destroyed(): boolean {        return this.#destroyed;    }    /**     *  Sub-classes may use this to shutdown any sockets or release their     *  resources and reject any pending requests.     *     *  Sub-classes **must** call ``super.destroy()``.     */    destroy(): void {        // Stop all listeners        this.removeAllListeners();        // Shut down all tiemrs        for (const timerId of this.#timers.keys()) {            this._clearTimeout(timerId);        }        this.#destroyed = true;    }    /**     *  Whether the provider is currently paused.     *     *  A paused provider will not emit any events, and generally should     *  not make any requests to the network, but that is up to sub-classes     *  to manage.     *     *  Setting ``paused = true`` is identical to calling ``.pause(false)``,     *  which will buffer any events that occur while paused until the     *  provider is unpaused.     */    get paused(): boolean { return (this.#pausedState != null); }    set paused(pause: boolean) {        if (!!pause === this.paused) { return; }        if (this.paused) {            this.resume();        } else {            this.pause(false);        }    }    /**     *  Pause the provider. If %%dropWhilePaused%%, any events that occur     *  while paused are dropped, otherwise all events will be emitted once     *  the provider is unpaused.     */    pause(dropWhilePaused?: boolean): void {        this.#lastBlockNumber = -1;        if (this.#pausedState != null) {            if (this.#pausedState == !!dropWhilePaused) { return; }            assert(false, "cannot change pause type; resume first", "UNSUPPORTED_OPERATION", {                operation: "pause"            });        }        this._forEachSubscriber((s) => s.pause(dropWhilePaused));        this.#pausedState = !!dropWhilePaused;        for (const timer of this.#timers.values()) {            // Clear the timer            if (timer.timer) { clearTimeout(timer.timer); }            // Remaining time needed for when we become unpaused            timer.time = getTime() - timer.time;        }    }    /**     *  Resume the provider.     */    resume(): void {        if (this.#pausedState == null) { return; }        this._forEachSubscriber((s) => s.resume());        this.#pausedState = null;        for (const timer of this.#timers.values()) {            // Remaining time when we were paused            let timeout = timer.time;            if (timeout < 0) { timeout = 0; }            // Start time (in cause paused, so we con compute remaininf time)            timer.time = getTime();            // Start the timer            setTimeout(timer.func, timeout);        }    }}function _parseString(result: string, start: number): null | string {    try {        const bytes = _parseBytes(result, start);        if (bytes) { return toUtf8String(bytes); }    } catch(error) { }    return null;}function _parseBytes(result: string, start: number): null | string {    if (result === "0x") { return null; }    try {        const offset = getNumber(dataSlice(result, start, start + 32));        const length = getNumber(dataSlice(result, offset, offset + 32));        return dataSlice(result, offset + 32, offset + 32 + length);    } catch (error) { }    return null;}function numPad(value: number): Uint8Array {    const result = toBeArray(value);    if (result.length > 32) { throw new Error("internal; should not happen"); }    const padded = new Uint8Array(32);    padded.set(result, 32 - result.length);    return padded;}function bytesPad(value: Uint8Array): Uint8Array {    if ((value.length % 32) === 0) { return value; }    const result = new Uint8Array(Math.ceil(value.length / 32) * 32);    result.set(value);    return result;}const empty: Uint8Array = new Uint8Array([ ]);// ABI Encodes a series of (bytes, bytes, ...)function encodeBytes(datas: Array<BytesLike>): string {    const result: Array<Uint8Array> = [ ];    let byteCount = 0;    // Add place-holders for pointers as we add items    for (let i = 0; i < datas.length; i++) {        result.push(empty);        byteCount += 32;    }    for (let i = 0; i < datas.length; i++) {        const data = getBytes(datas[i]);        // Update the bytes offset        result[i] = numPad(byteCount);        // The length and padded value of data        result.push(numPad(data.length));        result.push(bytesPad(data));        byteCount += 32 + Math.ceil(data.length / 32) * 32;    }    return concat(result);}const zeros = "0x0000000000000000000000000000000000000000000000000000000000000000"function parseOffchainLookup(data: string): CcipArgs {    const result: CcipArgs = {        sender: "", urls: [ ], calldata: "", selector: "", extraData: "", errorArgs: [ ]    };    assert(dataLength(data) >= 5 * 32, "insufficient OffchainLookup data", "OFFCHAIN_FAULT", {        reason: "insufficient OffchainLookup data"    });    const sender = dataSlice(data, 0, 32);    assert(dataSlice(sender, 0, 12) === dataSlice(zeros, 0, 12), "corrupt OffchainLookup sender", "OFFCHAIN_FAULT", {        reason: "corrupt OffchainLookup sender"    });    result.sender = dataSlice(sender, 12);    // Read the URLs from the response    try {        const urls: Array<string> = [];        const urlsOffset = getNumber(dataSlice(data, 32, 64));        const urlsLength = getNumber(dataSlice(data, urlsOffset, urlsOffset + 32));        const urlsData = dataSlice(data, urlsOffset + 32);        for (let u = 0; u < urlsLength; u++) {            const url = _parseString(urlsData, u * 32);            if (url == null) { throw new Error("abort"); }            urls.push(url);        }        result.urls = urls;    } catch (error) {        assert(false, "corrupt OffchainLookup urls", "OFFCHAIN_FAULT", {            reason: "corrupt OffchainLookup urls"        });    }    // Get the CCIP calldata to forward    try {        const calldata = _parseBytes(data, 64);        if (calldata == null) { throw new Error("abort"); }        result.calldata = calldata;    } catch (error) {        assert(false, "corrupt OffchainLookup calldata", "OFFCHAIN_FAULT", {            reason: "corrupt OffchainLookup calldata"        });    }    // Get the callbackSelector (bytes4)    assert(dataSlice(data, 100, 128) === dataSlice(zeros, 0, 28), "corrupt OffchainLookup callbaackSelector", "OFFCHAIN_FAULT", {        reason: "corrupt OffchainLookup callbaackSelector"    });    result.selector = dataSlice(data, 96, 100);    // Get the extra data to send back to the contract as context    try {        const extraData = _parseBytes(data, 128);        if (extraData == null) { throw new Error("abort"); }        result.extraData = extraData;    } catch (error) {        assert(false, "corrupt OffchainLookup extraData", "OFFCHAIN_FAULT", {            reason: "corrupt OffchainLookup extraData"        });    }    result.errorArgs = "sender,urls,calldata,selector,extraData".split(/,/).map((k) => (<any>result)[k])    return result;}
 |