| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954 | /** *  One of the most common ways to interact with the blockchain is *  by a node running a JSON-RPC interface which can be connected to, *  based on the transport, using: * *  - HTTP or HTTPS - [[JsonRpcProvider]] *  - WebSocket - [[WebSocketProvider]] *  - IPC - [[IpcSocketProvider]] * * @_section: api/providers/jsonrpc:JSON-RPC Provider  [about-jsonrpcProvider] */// @TODO:// - Add the batching API// https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=falseimport { AbiCoder } from "../abi/index.js";import { getAddress, resolveAddress } from "../address/index.js";import { TypedDataEncoder } from "../hash/index.js";import { accessListify } from "../transaction/index.js";import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, isError, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js";import { AbstractSigner } from "./abstract-signer.js";import { Network } from "./network.js";import { FilterIdEventSubscriber, FilterIdPendingSubscriber } from "./subscriber-filterid.js";import { PollingEventSubscriber } from "./subscriber-polling.js";const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g);//const Methods = "getAddress,then".split(/,/g);function deepCopy(value) {    if (value == null || Primitive.indexOf(typeof (value)) >= 0) {        return value;    }    // Keep any Addressable    if (typeof (value.getAddress) === "function") {        return value;    }    if (Array.isArray(value)) {        return (value.map(deepCopy));    }    if (typeof (value) === "object") {        return Object.keys(value).reduce((accum, key) => {            accum[key] = value[key];            return accum;        }, {});    }    throw new Error(`should not happen: ${value} (${typeof (value)})`);}function stall(duration) {    return new Promise((resolve) => { setTimeout(resolve, duration); });}function getLowerCase(value) {    if (value) {        return value.toLowerCase();    }    return value;}function isPollable(value) {    return (value && typeof (value.pollingInterval) === "number");}const defaultOptions = {    polling: false,    staticNetwork: null,    batchStallTime: 10,    batchMaxSize: (1 << 20),    batchMaxCount: 100,    cacheTimeout: 250,    pollingInterval: 4000};// @TODO: Unchecked Signersexport class JsonRpcSigner extends AbstractSigner {    address;    constructor(provider, address) {        super(provider);        address = getAddress(address);        defineProperties(this, { address });    }    connect(provider) {        assert(false, "cannot reconnect JsonRpcSigner", "UNSUPPORTED_OPERATION", {            operation: "signer.connect"        });    }    async getAddress() {        return this.address;    }    // JSON-RPC will automatially fill in nonce, etc. so we just check from    async populateTransaction(tx) {        return await this.populateCall(tx);    }    // Returns just the hash of the transaction after sent, which is what    // the bare JSON-RPC API does;    async sendUncheckedTransaction(_tx) {        const tx = deepCopy(_tx);        const promises = [];        // Make sure the from matches the sender        if (tx.from) {            const _from = tx.from;            promises.push((async () => {                const from = await resolveAddress(_from, this.provider);                assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), "from address mismatch", "transaction", _tx);                tx.from = from;            })());        }        else {            tx.from = this.address;        }        // The JSON-RPC for eth_sendTransaction uses 90000 gas; if the user        // wishes to use this, it is easy to specify explicitly, otherwise        // we look it up for them.        if (tx.gasLimit == null) {            promises.push((async () => {                tx.gasLimit = await this.provider.estimateGas({ ...tx, from: this.address });            })());        }        // The address may be an ENS name or Addressable        if (tx.to != null) {            const _to = tx.to;            promises.push((async () => {                tx.to = await resolveAddress(_to, this.provider);            })());        }        // Wait until all of our properties are filled in        if (promises.length) {            await Promise.all(promises);        }        const hexTx = this.provider.getRpcTransaction(tx);        return this.provider.send("eth_sendTransaction", [hexTx]);    }    async sendTransaction(tx) {        // This cannot be mined any earlier than any recent block        const blockNumber = await this.provider.getBlockNumber();        // Send the transaction        const hash = await this.sendUncheckedTransaction(tx);        // Unfortunately, JSON-RPC only provides and opaque transaction hash        // for a response, and we need the actual transaction, so we poll        // for it; it should show up very quickly        return await (new Promise((resolve, reject) => {            const timeouts = [1000, 100];            let invalids = 0;            const checkTx = async () => {                try {                    // Try getting the transaction                    const tx = await this.provider.getTransaction(hash);                    if (tx != null) {                        resolve(tx.replaceableTransaction(blockNumber));                        return;                    }                }                catch (error) {                    // If we were cancelled: stop polling.                    // If the data is bad: the node returns bad transactions                    // If the network changed: calling again will also fail                    // If unsupported: likely destroyed                    if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||                        isError(error, "NETWORK_ERROR") || isError(error, "UNSUPPORTED_OPERATION")) {                        if (error.info == null) {                            error.info = {};                        }                        error.info.sendTransactionHash = hash;                        reject(error);                        return;                    }                    // Stop-gap for misbehaving backends; see #4513                    if (isError(error, "INVALID_ARGUMENT")) {                        invalids++;                        if (error.info == null) {                            error.info = {};                        }                        error.info.sendTransactionHash = hash;                        if (invalids > 10) {                            reject(error);                            return;                        }                    }                    // Notify anyone that cares; but we will try again, since                    // it is likely an intermittent service error                    this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));                }                // Wait another 4 seconds                this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);            };            checkTx();        }));    }    async signTransaction(_tx) {        const tx = deepCopy(_tx);        // Make sure the from matches the sender        if (tx.from) {            const from = await resolveAddress(tx.from, this.provider);            assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), "from address mismatch", "transaction", _tx);            tx.from = from;        }        else {            tx.from = this.address;        }        const hexTx = this.provider.getRpcTransaction(tx);        return await this.provider.send("eth_signTransaction", [hexTx]);    }    async signMessage(_message) {        const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message);        return await this.provider.send("personal_sign", [            hexlify(message), this.address.toLowerCase()        ]);    }    async signTypedData(domain, types, _value) {        const value = deepCopy(_value);        // Populate any ENS names (in-place)        const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (value) => {            const address = await resolveAddress(value);            assertArgument(address != null, "TypedData does not support null address", "value", value);            return address;        });        return await this.provider.send("eth_signTypedData_v4", [            this.address.toLowerCase(),            JSON.stringify(TypedDataEncoder.getPayload(populated.domain, types, populated.value))        ]);    }    async unlock(password) {        return this.provider.send("personal_unlockAccount", [            this.address.toLowerCase(), password, null        ]);    }    // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign    async _legacySignMessage(_message) {        const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message);        return await this.provider.send("eth_sign", [            this.address.toLowerCase(), hexlify(message)        ]);    }}/** *  The JsonRpcApiProvider is an abstract class and **MUST** be *  sub-classed. * *  It provides the base for all JSON-RPC-based Provider interaction. * *  Sub-classing Notes: *  - a sub-class MUST override _send *  - a sub-class MUST call the `_start()` method once connected */export class JsonRpcApiProvider extends AbstractProvider {    #options;    // The next ID to use for the JSON-RPC ID field    #nextId;    // Payloads are queued and triggered in batches using the drainTimer    #payloads;    #drainTimer;    #notReady;    #network;    #pendingDetectNetwork;    #scheduleDrain() {        if (this.#drainTimer) {            return;        }        // If we aren't using batching, no harm in sending it immediately        const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");        this.#drainTimer = setTimeout(() => {            this.#drainTimer = null;            const payloads = this.#payloads;            this.#payloads = [];            while (payloads.length) {                // Create payload batches that satisfy our batch constraints                const batch = [(payloads.shift())];                while (payloads.length) {                    if (batch.length === this.#options.batchMaxCount) {                        break;                    }                    batch.push((payloads.shift()));                    const bytes = JSON.stringify(batch.map((p) => p.payload));                    if (bytes.length > this.#options.batchMaxSize) {                        payloads.unshift((batch.pop()));                        break;                    }                }                // Process the result to each payload                (async () => {                    const payload = ((batch.length === 1) ? batch[0].payload : batch.map((p) => p.payload));                    this.emit("debug", { action: "sendRpcPayload", payload });                    try {                        const result = await this._send(payload);                        this.emit("debug", { action: "receiveRpcResult", result });                        // Process results in batch order                        for (const { resolve, reject, payload } of batch) {                            if (this.destroyed) {                                reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: payload.method }));                                continue;                            }                            // Find the matching result                            const resp = result.filter((r) => (r.id === payload.id))[0];                            // No result; the node failed us in unexpected ways                            if (resp == null) {                                const error = makeError("missing response for request", "BAD_DATA", {                                    value: result, info: { payload }                                });                                this.emit("error", error);                                reject(error);                                continue;                            }                            // The response is an error                            if ("error" in resp) {                                reject(this.getRpcError(payload, resp));                                continue;                            }                            // All good; send the result                            resolve(resp.result);                        }                    }                    catch (error) {                        this.emit("debug", { action: "receiveRpcError", error });                        for (const { reject } of batch) {                            // @TODO: augment the error with the payload                            reject(error);                        }                    }                })();            }        }, stallTime);    }    constructor(network, options) {        super(network, options);        this.#nextId = 1;        this.#options = Object.assign({}, defaultOptions, options || {});        this.#payloads = [];        this.#drainTimer = null;        this.#network = null;        this.#pendingDetectNetwork = null;        {            let resolve = null;            const promise = new Promise((_resolve) => {                resolve = _resolve;            });            this.#notReady = { promise, resolve };        }        const staticNetwork = this._getOption("staticNetwork");        if (typeof (staticNetwork) === "boolean") {            assertArgument(!staticNetwork || network !== "any", "staticNetwork cannot be used on special network 'any'", "options", options);            if (staticNetwork && network != null) {                this.#network = Network.from(network);            }        }        else if (staticNetwork) {            // Make sure any static network is compatbile with the provided netwrok            assertArgument(network == null || staticNetwork.matches(network), "staticNetwork MUST match network object", "options", options);            this.#network = staticNetwork;        }    }    /**     *  Returns the value associated with the option %%key%%.     *     *  Sub-classes can use this to inquire about configuration options.     */    _getOption(key) {        return this.#options[key];    }    /**     *  Gets the [[Network]] this provider has committed to. On each call, the network     *  is detected, and if it has changed, the call will reject.     */    get _network() {        assert(this.#network, "network is not available yet", "NETWORK_ERROR");        return this.#network;    }    /**     *  Resolves to the non-normalized value by performing %%req%%.     *     *  Sub-classes may override this to modify behavior of actions,     *  and should generally call ``super._perform`` as a fallback.     */    async _perform(req) {        // Legacy networks do not like the type field being passed along (which        // is fair), so we delete type if it is 0 and a non-EIP-1559 network        if (req.method === "call" || req.method === "estimateGas") {            let tx = req.transaction;            if (tx && tx.type != null && getBigInt(tx.type)) {                // If there are no EIP-1559 or newer properties, it might be pre-EIP-1559                if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {                    const feeData = await this.getFeeData();                    if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {                        // Network doesn't know about EIP-1559 (and hence type)                        req = Object.assign({}, req, {                            transaction: Object.assign({}, tx, { type: undefined })                        });                    }                }            }        }        const request = this.getRpcRequest(req);        if (request != null) {            return await this.send(request.method, request.args);        }        return super._perform(req);    }    /**     *  Sub-classes may override this; it detects the *actual* network that     *  we are **currently** connected to.     *     *  Keep in mind that [[send]] may only be used once [[ready]], otherwise the     *  _send primitive must be used instead.     */    async _detectNetwork() {        const network = this._getOption("staticNetwork");        if (network) {            if (network === true) {                if (this.#network) {                    return this.#network;                }            }            else {                return network;            }        }        if (this.#pendingDetectNetwork) {            return await this.#pendingDetectNetwork;        }        // If we are ready, use ``send``, which enabled requests to be batched        if (this.ready) {            this.#pendingDetectNetwork = (async () => {                try {                    const result = Network.from(getBigInt(await this.send("eth_chainId", [])));                    this.#pendingDetectNetwork = null;                    return result;                }                catch (error) {                    this.#pendingDetectNetwork = null;                    throw error;                }            })();            return await this.#pendingDetectNetwork;        }        // We are not ready yet; use the primitive _send        this.#pendingDetectNetwork = (async () => {            const payload = {                id: this.#nextId++, method: "eth_chainId", params: [], jsonrpc: "2.0"            };            this.emit("debug", { action: "sendRpcPayload", payload });            let result;            try {                result = (await this._send(payload))[0];                this.#pendingDetectNetwork = null;            }            catch (error) {                this.#pendingDetectNetwork = null;                this.emit("debug", { action: "receiveRpcError", error });                throw error;            }            this.emit("debug", { action: "receiveRpcResult", result });            if ("result" in result) {                return Network.from(getBigInt(result.result));            }            throw this.getRpcError(payload, result);        })();        return await this.#pendingDetectNetwork;    }    /**     *  Sub-classes **MUST** call this. Until [[_start]] has been called, no calls     *  will be passed to [[_send]] from [[send]]. If it is overridden, then     *  ``super._start()`` **MUST** be called.     *     *  Calling it multiple times is safe and has no effect.     */    _start() {        if (this.#notReady == null || this.#notReady.resolve == null) {            return;        }        this.#notReady.resolve();        this.#notReady = null;        (async () => {            // Bootstrap the network            while (this.#network == null && !this.destroyed) {                try {                    this.#network = await this._detectNetwork();                }                catch (error) {                    if (this.destroyed) {                        break;                    }                    console.log("JsonRpcProvider failed to detect network and cannot start up; retry in 1s (perhaps the URL is wrong or the node is not started)");                    this.emit("error", makeError("failed to bootstrap network detection", "NETWORK_ERROR", { event: "initial-network-discovery", info: { error } }));                    await stall(1000);                }            }            // Start dispatching requests            this.#scheduleDrain();        })();    }    /**     *  Resolves once the [[_start]] has been called. This can be used in     *  sub-classes to defer sending data until the connection has been     *  established.     */    async _waitUntilReady() {        if (this.#notReady == null) {            return;        }        return await this.#notReady.promise;    }    /**     *  Return a Subscriber that will manage the %%sub%%.     *     *  Sub-classes may override this to modify the behavior of     *  subscription management.     */    _getSubscriber(sub) {        // Pending Filters aren't availble via polling        if (sub.type === "pending") {            return new FilterIdPendingSubscriber(this);        }        if (sub.type === "event") {            if (this._getOption("polling")) {                return new PollingEventSubscriber(this, sub.filter);            }            return new FilterIdEventSubscriber(this, sub.filter);        }        // Orphaned Logs are handled automatically, by the filter, since        // logs with removed are emitted by it        if (sub.type === "orphan" && sub.filter.orphan === "drop-log") {            return new UnmanagedSubscriber("orphan");        }        return super._getSubscriber(sub);    }    /**     *  Returns true only if the [[_start]] has been called.     */    get ready() { return this.#notReady == null; }    /**     *  Returns %%tx%% as a normalized JSON-RPC transaction request,     *  which has all values hexlified and any numeric values converted     *  to Quantity values.     */    getRpcTransaction(tx) {        const result = {};        // JSON-RPC now requires numeric values to be "quantity" values        ["chainId", "gasLimit", "gasPrice", "type", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "value"].forEach((key) => {            if (tx[key] == null) {                return;            }            let dstKey = key;            if (key === "gasLimit") {                dstKey = "gas";            }            result[dstKey] = toQuantity(getBigInt(tx[key], `tx.${key}`));        });        // Make sure addresses and data are lowercase        ["from", "to", "data"].forEach((key) => {            if (tx[key] == null) {                return;            }            result[key] = hexlify(tx[key]);        });        // Normalize the access list object        if (tx.accessList) {            result["accessList"] = accessListify(tx.accessList);        }        if (tx.blobVersionedHashes) {            // @TODO: Remove this <any> case once EIP-4844 added to prepared tx            result["blobVersionedHashes"] = tx.blobVersionedHashes.map(h => h.toLowerCase());        }        // @TODO: blobs should probably also be copied over, optionally        // accounting for the kzg property to backfill blobVersionedHashes        // using the commitment. Or should that be left as an exercise to        // the caller?        return result;    }    /**     *  Returns the request method and arguments required to perform     *  %%req%%.     */    getRpcRequest(req) {        switch (req.method) {            case "chainId":                return { method: "eth_chainId", args: [] };            case "getBlockNumber":                return { method: "eth_blockNumber", args: [] };            case "getGasPrice":                return { method: "eth_gasPrice", args: [] };            case "getPriorityFee":                return { method: "eth_maxPriorityFeePerGas", args: [] };            case "getBalance":                return {                    method: "eth_getBalance",                    args: [getLowerCase(req.address), req.blockTag]                };            case "getTransactionCount":                return {                    method: "eth_getTransactionCount",                    args: [getLowerCase(req.address), req.blockTag]                };            case "getCode":                return {                    method: "eth_getCode",                    args: [getLowerCase(req.address), req.blockTag]                };            case "getStorage":                return {                    method: "eth_getStorageAt",                    args: [                        getLowerCase(req.address),                        ("0x" + req.position.toString(16)),                        req.blockTag                    ]                };            case "broadcastTransaction":                return {                    method: "eth_sendRawTransaction",                    args: [req.signedTransaction]                };            case "getBlock":                if ("blockTag" in req) {                    return {                        method: "eth_getBlockByNumber",                        args: [req.blockTag, !!req.includeTransactions]                    };                }                else if ("blockHash" in req) {                    return {                        method: "eth_getBlockByHash",                        args: [req.blockHash, !!req.includeTransactions]                    };                }                break;            case "getTransaction":                return {                    method: "eth_getTransactionByHash",                    args: [req.hash]                };            case "getTransactionReceipt":                return {                    method: "eth_getTransactionReceipt",                    args: [req.hash]                };            case "call":                return {                    method: "eth_call",                    args: [this.getRpcTransaction(req.transaction), req.blockTag]                };            case "estimateGas": {                return {                    method: "eth_estimateGas",                    args: [this.getRpcTransaction(req.transaction)]                };            }            case "getLogs":                if (req.filter && req.filter.address != null) {                    if (Array.isArray(req.filter.address)) {                        req.filter.address = req.filter.address.map(getLowerCase);                    }                    else {                        req.filter.address = getLowerCase(req.filter.address);                    }                }                return { method: "eth_getLogs", args: [req.filter] };        }        return null;    }    /**     *  Returns an ethers-style Error for the given JSON-RPC error     *  %%payload%%, coalescing the various strings and error shapes     *  that different nodes return, coercing them into a machine-readable     *  standardized error.     */    getRpcError(payload, _error) {        const { method } = payload;        const { error } = _error;        if (method === "eth_estimateGas" && error.message) {            const msg = error.message;            if (!msg.match(/revert/i) && msg.match(/insufficient funds/i)) {                return makeError("insufficient funds", "INSUFFICIENT_FUNDS", {                    transaction: (payload.params[0]),                    info: { payload, error }                });            }        }        if (method === "eth_call" || method === "eth_estimateGas") {            const result = spelunkData(error);            const e = AbiCoder.getBuiltinCallException((method === "eth_call") ? "call" : "estimateGas", (payload.params[0]), (result ? result.data : null));            e.info = { error, payload };            return e;        }        // Only estimateGas and call can return arbitrary contract-defined text, so now we        // we can process text safely.        const message = JSON.stringify(spelunkMessage(error));        if (typeof (error.message) === "string" && error.message.match(/user denied|ethers-user-denied/i)) {            const actionMap = {                eth_sign: "signMessage",                personal_sign: "signMessage",                eth_signTypedData_v4: "signTypedData",                eth_signTransaction: "signTransaction",                eth_sendTransaction: "sendTransaction",                eth_requestAccounts: "requestAccess",                wallet_requestAccounts: "requestAccess",            };            return makeError(`user rejected action`, "ACTION_REJECTED", {                action: (actionMap[method] || "unknown"),                reason: "rejected",                info: { payload, error }            });        }        if (method === "eth_sendRawTransaction" || method === "eth_sendTransaction") {            const transaction = (payload.params[0]);            if (message.match(/insufficient funds|base fee exceeds gas limit/i)) {                return makeError("insufficient funds for intrinsic transaction cost", "INSUFFICIENT_FUNDS", {                    transaction, info: { error }                });            }            if (message.match(/nonce/i) && message.match(/too low/i)) {                return makeError("nonce has already been used", "NONCE_EXPIRED", { transaction, info: { error } });            }            // "replacement transaction underpriced"            if (message.match(/replacement transaction/i) && message.match(/underpriced/i)) {                return makeError("replacement fee too low", "REPLACEMENT_UNDERPRICED", { transaction, info: { error } });            }            if (message.match(/only replay-protected/i)) {                return makeError("legacy pre-eip-155 transactions not supported", "UNSUPPORTED_OPERATION", {                    operation: method, info: { transaction, info: { error } }                });            }        }        let unsupported = !!message.match(/the method .* does not exist/i);        if (!unsupported) {            if (error && error.details && error.details.startsWith("Unauthorized method:")) {                unsupported = true;            }        }        if (unsupported) {            return makeError("unsupported operation", "UNSUPPORTED_OPERATION", {                operation: payload.method, info: { error, payload }            });        }        return makeError("could not coalesce error", "UNKNOWN_ERROR", { error, payload });    }    /**     *  Requests the %%method%% with %%params%% via the JSON-RPC protocol     *  over the underlying channel. This can be used to call methods     *  on the backend that do not have a high-level API within the Provider     *  API.     *     *  This method queues requests according to the batch constraints     *  in the options, assigns the request a unique ID.     *     *  **Do NOT override** this method in sub-classes; instead     *  override [[_send]] or force the options values in the     *  call to the constructor to modify this method's behavior.     */    send(method, params) {        // @TODO: cache chainId?? purge on switch_networks        // We have been destroyed; no operations are supported anymore        if (this.destroyed) {            return Promise.reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: method }));        }        const id = this.#nextId++;        const promise = new Promise((resolve, reject) => {            this.#payloads.push({                resolve, reject,                payload: { method, params, id, jsonrpc: "2.0" }            });        });        // If there is not a pending drainTimer, set one        this.#scheduleDrain();        return promise;    }    /**     *  Resolves to the [[Signer]] account for  %%address%% managed by     *  the client.     *     *  If the %%address%% is a number, it is used as an index in the     *  the accounts from [[listAccounts]].     *     *  This can only be used on clients which manage accounts (such as     *  Geth with imported account or MetaMask).     *     *  Throws if the account doesn't exist.     */    async getSigner(address) {        if (address == null) {            address = 0;        }        const accountsPromise = this.send("eth_accounts", []);        // Account index        if (typeof (address) === "number") {            const accounts = (await accountsPromise);            if (address >= accounts.length) {                throw new Error("no such account");            }            return new JsonRpcSigner(this, accounts[address]);        }        const { accounts } = await resolveProperties({            network: this.getNetwork(),            accounts: accountsPromise        });        // Account address        address = getAddress(address);        for (const account of accounts) {            if (getAddress(account) === address) {                return new JsonRpcSigner(this, address);            }        }        throw new Error("invalid account");    }    async listAccounts() {        const accounts = await this.send("eth_accounts", []);        return accounts.map((a) => new JsonRpcSigner(this, a));    }    destroy() {        // Stop processing requests        if (this.#drainTimer) {            clearTimeout(this.#drainTimer);            this.#drainTimer = null;        }        // Cancel all pending requests        for (const { payload, reject } of this.#payloads) {            reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: payload.method }));        }        this.#payloads = [];        // Parent clean-up        super.destroy();    }}// @TODO: remove this in v7, it is not exported because this functionality// is exposed in the JsonRpcApiProvider by setting polling to true. It should// be safe to remove regardless, because it isn't reachable, but just in case./** *  @_ignore: */export class JsonRpcApiPollingProvider extends JsonRpcApiProvider {    #pollingInterval;    constructor(network, options) {        super(network, options);        let pollingInterval = this._getOption("pollingInterval");        if (pollingInterval == null) {            pollingInterval = defaultOptions.pollingInterval;        }        this.#pollingInterval = pollingInterval;    }    _getSubscriber(sub) {        const subscriber = super._getSubscriber(sub);        if (isPollable(subscriber)) {            subscriber.pollingInterval = this.#pollingInterval;        }        return subscriber;    }    /**     *  The polling interval (default: 4000 ms)     */    get pollingInterval() { return this.#pollingInterval; }    set pollingInterval(value) {        if (!Number.isInteger(value) || value < 0) {            throw new Error("invalid interval");        }        this.#pollingInterval = value;        this._forEachSubscriber((sub) => {            if (isPollable(sub)) {                sub.pollingInterval = this.#pollingInterval;            }        });    }}/** *  The JsonRpcProvider is one of the most common Providers, *  which performs all operations over HTTP (or HTTPS) requests. * *  Events are processed by polling the backend for the current block *  number; when it advances, all block-base events are then checked *  for updates. */export class JsonRpcProvider extends JsonRpcApiPollingProvider {    #connect;    constructor(url, network, options) {        if (url == null) {            url = "http:/\/localhost:8545";        }        super(network, options);        if (typeof (url) === "string") {            this.#connect = new FetchRequest(url);        }        else {            this.#connect = url.clone();        }    }    _getConnection() {        return this.#connect.clone();    }    async send(method, params) {        // All requests are over HTTP, so we can just start handling requests        // We do this here rather than the constructor so that we don't send any        // requests to the network (i.e. eth_chainId) until we absolutely have to.        await this._start();        return await super.send(method, params);    }    async _send(payload) {        // Configure a POST connection for the requested method        const request = this._getConnection();        request.body = JSON.stringify(payload);        request.setHeader("content-type", "application/json");        const response = await request.send();        response.assertOk();        let resp = response.bodyJson;        if (!Array.isArray(resp)) {            resp = [resp];        }        return resp;    }}function spelunkData(value) {    if (value == null) {        return null;    }    // These *are* the droids we're looking for.    if (typeof (value.message) === "string" && value.message.match(/revert/i) && isHexString(value.data)) {        return { message: value.message, data: value.data };    }    // Spelunk further...    if (typeof (value) === "object") {        for (const key in value) {            const result = spelunkData(value[key]);            if (result) {                return result;            }        }        return null;    }    // Might be a JSON string we can further descend...    if (typeof (value) === "string") {        try {            return spelunkData(JSON.parse(value));        }        catch (error) { }    }    return null;}function _spelunkMessage(value, result) {    if (value == null) {        return;    }    // These *are* the droids we're looking for.    if (typeof (value.message) === "string") {        result.push(value.message);    }    // Spelunk further...    if (typeof (value) === "object") {        for (const key in value) {            _spelunkMessage(value[key], result);        }    }    // Might be a JSON string we can further descend...    if (typeof (value) === "string") {        try {            return _spelunkMessage(JSON.parse(value), result);        }        catch (error) { }    }}function spelunkMessage(value) {    const result = [];    _spelunkMessage(value, result);    return result;}//# sourceMappingURL=provider-jsonrpc.js.map
 |