12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409 |
- "use strict";
- /**
- * 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]
- */
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.AbstractProvider = exports.UnmanagedSubscriber = void 0;
- // @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.
- const index_js_1 = require("../address/index.js");
- const index_js_2 = require("../constants/index.js");
- const index_js_3 = require("../contract/index.js");
- const index_js_4 = require("../hash/index.js");
- const index_js_5 = require("../transaction/index.js");
- const index_js_6 = require("../utils/index.js");
- const ens_resolver_js_1 = require("./ens-resolver.js");
- const format_js_1 = require("./format.js");
- const network_js_1 = require("./network.js");
- const provider_js_1 = require("./provider.js");
- const subscriber_polling_js_1 = require("./subscriber-polling.js");
- // Constants
- const BN_2 = BigInt(2);
- const MAX_CCIP_REDIRECTS = 10;
- function isPromise(value) {
- return (value && typeof (value.then) === "function");
- }
- function getTag(prefix, value) {
- 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;
- }, {});
- }
- return v;
- });
- }
- /**
- * 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.
- */
- class UnmanagedSubscriber {
- /**
- * The name fof the event.
- */
- name;
- /**
- * Create a new UnmanagedSubscriber with %%name%%.
- */
- constructor(name) { (0, index_js_6.defineProperties)(this, { name }); }
- start() { }
- stop() { }
- pause(dropWhilePaused) { }
- resume() { }
- }
- exports.UnmanagedSubscriber = UnmanagedSubscriber;
- function copy(value) {
- return JSON.parse(JSON.stringify(value));
- }
- function concisify(items) {
- items = Array.from((new Set(items)).values());
- items.sort();
- return items;
- }
- async function getSubscription(_event, provider) {
- 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 ((0, index_js_6.isHexString)(_event, 32)) {
- const hash = _event.toLowerCase();
- return { type: "transaction", tag: getTag("tx", { hash }), hash };
- }
- if (_event.orphan) {
- const event = _event;
- // @TODO: Should lowercase and whatnot things here instead of copy...
- return { type: "orphan", tag: getTag("orphan", event), filter: copy(event) };
- }
- if ((_event.address || _event.topics)) {
- const event = _event;
- const filter = {
- 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 = [];
- const promises = [];
- const addAddress = (addr) => {
- if ((0, index_js_6.isHexString)(addr)) {
- addresses.push(addr);
- }
- else {
- promises.push((async () => {
- addresses.push(await (0, index_js_1.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" };
- }
- (0, index_js_6.assertArgument)(false, "unknown ProviderEvent", "event", _event);
- }
- function getTime() { return (new Date()).getTime(); }
- const defaultOptions = {
- cacheTimeout: 250,
- pollingInterval: 4000
- };
- /**
- * 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.
- */
- class AbstractProvider {
- #subs;
- #plugins;
- // null=unpaused, true=paused+dropWhilePaused, false=paused
- #pausedState;
- #destroyed;
- #networkPromise;
- #anyNetwork;
- #performCache;
- // The most recent block number if running an event or -1 if no "block" event
- #lastBlockNumber;
- #nextTimer;
- #timers;
- #disableCcipRead;
- #options;
- /**
- * Create a new **AbstractProvider** connected to %%network%%, or
- * use the various network detection capabilities to discover the
- * [[Network]] if necessary.
- */
- constructor(_network, options) {
- this.#options = Object.assign({}, defaultOptions, options || {});
- if (_network === "any") {
- this.#anyNetwork = true;
- this.#networkPromise = null;
- }
- else if (_network) {
- const network = network_js_1.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() { return this.#options.pollingInterval; }
- /**
- * Returns ``this``, to allow an **AbstractProvider** to implement
- * the [[ContractRunner]] interface.
- */
- get provider() { return this; }
- /**
- * Returns all the registered plug-ins.
- */
- get plugins() {
- return Array.from(this.#plugins.values());
- }
- /**
- * Attach a new plug-in.
- */
- attachPlugin(plugin) {
- 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(name) {
- return (this.#plugins.get(name)) || null;
- }
- /**
- * Prevent any CCIP-read operation, regardless of whether requested
- * in a [[call]] using ``enableCcipRead``.
- */
- get disableCcipRead() { return this.#disableCcipRead; }
- set disableCcipRead(value) { this.#disableCcipRead = !!value; }
- // Shares multiple identical requests made during the same 250ms
- async #perform(req) {
- 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, calldata, urls) {
- if (this.disableCcipRead || urls.length === 0 || tx.to == null) {
- return null;
- }
- const sender = tx.to.toLowerCase();
- const data = calldata.toLowerCase();
- const errorMessages = [];
- 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 index_js_6.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;
- try {
- resp = await request.send();
- }
- catch (error) {
- // ...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
- (0, index_js_6.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);
- }
- (0, index_js_6.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, network) {
- return new provider_js_1.Block((0, format_js_1.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, network) {
- return new provider_js_1.Log((0, format_js_1.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, network) {
- return new provider_js_1.TransactionReceipt((0, format_js_1.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, network) {
- return new provider_js_1.TransactionResponse((0, format_js_1.formatTransactionResponse)(tx), this);
- }
- /**
- * Resolves to the Network, forcing a network detection using whatever
- * technique the sub-class requires.
- *
- * Sub-classes **must** override this.
- */
- _detectNetwork() {
- (0, index_js_6.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(req) {
- (0, index_js_6.assert)(false, `unsupported method: ${req.method}`, "UNSUPPORTED_OPERATION", {
- operation: req.method,
- info: req
- });
- }
- // State
- async getBlockNumber() {
- const blockNumber = (0, index_js_6.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) {
- return (0, index_js_1.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) {
- if (blockTag == null) {
- return "latest";
- }
- switch (blockTag) {
- case "earliest":
- return "0x0";
- case "finalized":
- case "latest":
- case "pending":
- case "safe":
- return blockTag;
- }
- if ((0, index_js_6.isHexString)(blockTag)) {
- if ((0, index_js_6.isHexString)(blockTag, 32)) {
- return blockTag;
- }
- return (0, index_js_6.toQuantity)(blockTag);
- }
- if (typeof (blockTag) === "bigint") {
- blockTag = (0, index_js_6.getNumber)(blockTag, "blockTag");
- }
- if (typeof (blockTag) === "number") {
- if (blockTag >= 0) {
- return (0, index_js_6.toQuantity)(blockTag);
- }
- if (this.#lastBlockNumber >= 0) {
- return (0, index_js_6.toQuantity)(this.#lastBlockNumber + blockTag);
- }
- return this.getBlockNumber().then((b) => (0, index_js_6.toQuantity)(b + blockTag));
- }
- (0, index_js_6.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) {
- // 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, fromBlock, toBlock) => {
- let address = 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 = {};
- 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 = [];
- 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;
- if ("fromBlock" in filter) {
- fromBlock = this._getBlockTag(filter.fromBlock);
- }
- let toBlock = 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(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) {
- const request = (0, provider_js_1.copyRequest)(_request);
- const promises = [];
- ["to", "from"].forEach((key) => {
- if (request[key] == null) {
- return;
- }
- const addr = (0, index_js_1.resolveAddress)(request[key], this);
- if (isPromise(addr)) {
- promises.push((async function () { request[key] = await addr; })());
- }
- else {
- 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() {
- // 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,
- 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
- (0, index_js_6.assert)(false, `network changed: ${expected.chainId} => ${actual.chainId} `, "NETWORK_ERROR", {
- event: "changed"
- });
- }
- }
- return expected.clone();
- }
- async getFeeData() {
- const network = await this.getNetwork();
- const getFeeDataFunc = async () => {
- const { _block, gasPrice, priorityFee } = await (0, index_js_6.resolveProperties)({
- _block: this.#getBlock("latest", false),
- gasPrice: ((async () => {
- try {
- const value = await this.#perform({ method: "getGasPrice" });
- return (0, index_js_6.getBigInt)(value, "%response");
- }
- catch (error) { }
- return null;
- })()),
- priorityFee: ((async () => {
- try {
- const value = await this.#perform({ method: "getPriorityFee" });
- return (0, index_js_6.getBigInt)(value, "%response");
- }
- catch (error) { }
- return null;
- })())
- });
- let maxFeePerGas = null;
- let maxPriorityFeePerGas = 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 provider_js_1.FeeData(gasPrice, maxFeePerGas, maxPriorityFeePerGas);
- };
- // Check for a FeeDataNetWorkPlugin
- const plugin = network.getPlugin("org.ethers.plugins.network.FetchUrlFeeDataPlugin");
- if (plugin) {
- const req = new index_js_6.FetchRequest(plugin.url);
- const feeData = await plugin.processFunc(getFeeDataFunc, this, req);
- return new provider_js_1.FeeData(feeData.gasPrice, feeData.maxFeePerGas, feeData.maxPriorityFeePerGas);
- }
- return await getFeeDataFunc();
- }
- async estimateGas(_tx) {
- let tx = this._getTransactionRequest(_tx);
- if (isPromise(tx)) {
- tx = await tx;
- }
- return (0, index_js_6.getBigInt)(await this.#perform({
- method: "estimateGas", transaction: tx
- }), "%response");
- }
- async #call(tx, blockTag, attempt) {
- (0, index_js_6.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 = (0, provider_js_1.copyRequest)(tx);
- try {
- return (0, index_js_6.hexlify)(await this._perform({ method: "call", transaction, blockTag }));
- }
- catch (error) {
- // CCIP Read OffchainLookup
- if (!this.disableCcipRead && (0, index_js_6.isCallException)(error) && error.data && attempt >= 0 && blockTag === "latest" && transaction.to != null && (0, index_js_6.dataSlice)(error.data, 0, 4) === "0x556f1830") {
- const data = error.data;
- const txSender = await (0, index_js_1.resolveAddress)(transaction.to, this);
- // Parse the CCIP Read Arguments
- let ccipArgs;
- try {
- ccipArgs = parseOffchainLookup((0, index_js_6.dataSlice)(error.data, 4));
- }
- catch (error) {
- (0, index_js_6.assert)(false, error.message, "OFFCHAIN_FAULT", {
- reason: "BAD_DATA", transaction, info: { data }
- });
- }
- // Check the sender of the OffchainLookup matches the transaction
- (0, index_js_6.assert)(ccipArgs.sender.toLowerCase() === txSender.toLowerCase(), "CCIP Read sender mismatch", "CALL_EXCEPTION", {
- action: "call",
- data,
- reason: "OffchainLookup",
- transaction: transaction,
- 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);
- (0, index_js_6.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: (0, index_js_6.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(promise) {
- const { value } = await (0, index_js_6.resolveProperties)({
- network: this.getNetwork(),
- value: promise
- });
- return value;
- }
- async call(_tx) {
- const { tx, blockTag } = await (0, index_js_6.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, _address, _blockTag) {
- let address = this._getAddress(_address);
- let blockTag = 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, blockTag) {
- return (0, index_js_6.getBigInt)(await this.#getAccountValue({ method: "getBalance" }, address, blockTag), "%response");
- }
- async getTransactionCount(address, blockTag) {
- return (0, index_js_6.getNumber)(await this.#getAccountValue({ method: "getTransactionCount" }, address, blockTag), "%response");
- }
- async getCode(address, blockTag) {
- return (0, index_js_6.hexlify)(await this.#getAccountValue({ method: "getCode" }, address, blockTag));
- }
- async getStorage(address, _position, blockTag) {
- const position = (0, index_js_6.getBigInt)(_position, "position");
- return (0, index_js_6.hexlify)(await this.#getAccountValue({ method: "getStorage", position }, address, blockTag));
- }
- // Write
- async broadcastTransaction(signedTx) {
- const { blockNumber, hash, network } = await (0, index_js_6.resolveProperties)({
- blockNumber: this.getBlockNumber(),
- hash: this._perform({
- method: "broadcastTransaction",
- signedTransaction: signedTx
- }),
- network: this.getNetwork()
- });
- const tx = index_js_5.Transaction.from(signedTx);
- if (tx.hash !== hash) {
- throw new Error("@TODO: the returned hash did not match");
- }
- return this._wrapTransactionResponse(tx, network).replaceableTransaction(blockNumber);
- }
- async #getBlock(block, includeTransactions) {
- // @TODO: Add CustomBlockPlugin check
- if ((0, index_js_6.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, prefetchTxs) {
- const { network, params } = await (0, index_js_6.resolveProperties)({
- network: this.getNetwork(),
- params: this.#getBlock(block, !!prefetchTxs)
- });
- if (params == null) {
- return null;
- }
- return this._wrapBlock(params, network);
- }
- async getTransaction(hash) {
- const { network, params } = await (0, index_js_6.resolveProperties)({
- network: this.getNetwork(),
- params: this.#perform({ method: "getTransaction", hash })
- });
- if (params == null) {
- return null;
- }
- return this._wrapTransactionResponse(params, network);
- }
- async getTransactionReceipt(hash) {
- const { network, params } = await (0, index_js_6.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) {
- const { result } = await (0, index_js_6.resolveProperties)({
- network: this.getNetwork(),
- result: this.#perform({ method: "getTransactionResult", hash })
- });
- if (result == null) {
- return null;
- }
- return (0, index_js_6.hexlify)(result);
- }
- // Bloom-filter Queries
- async getLogs(_filter) {
- let filter = this._getFilter(_filter);
- if (isPromise(filter)) {
- filter = await filter;
- }
- const { network, params } = await (0, index_js_6.resolveProperties)({
- network: this.getNetwork(),
- params: this.#perform({ method: "getLogs", filter })
- });
- return params.map((p) => this._wrapLog(p, network));
- }
- // ENS
- _getProvider(chainId) {
- (0, index_js_6.assert)(false, "provider cannot connect to target network", "UNSUPPORTED_OPERATION", {
- operation: "_getProvider()"
- });
- }
- async getResolver(name) {
- return await ens_resolver_js_1.EnsResolver.fromName(this, name);
- }
- async getAvatar(name) {
- const resolver = await this.getResolver(name);
- if (resolver) {
- return await resolver.getAvatar();
- }
- return null;
- }
- async resolveName(name) {
- const resolver = await this.getResolver(name);
- if (resolver) {
- return await resolver.getAddress();
- }
- return null;
- }
- async lookupAddress(address) {
- address = (0, index_js_1.getAddress)(address);
- const node = (0, index_js_4.namehash)(address.substring(2).toLowerCase() + ".addr.reverse");
- try {
- const ensAddr = await ens_resolver_js_1.EnsResolver.getEnsAddress(this);
- const ensContract = new index_js_3.Contract(ensAddr, [
- "function resolver(bytes32) view returns (address)"
- ], this);
- const resolver = await ensContract.resolver(node);
- if (resolver == null || resolver === index_js_2.ZeroAddress) {
- return null;
- }
- const resolverContract = new index_js_3.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 ((0, index_js_6.isError)(error, "BAD_DATA") && error.value === "0x") {
- return null;
- }
- // Something reerted
- if ((0, index_js_6.isError)(error, "CALL_EXCEPTION")) {
- return null;
- }
- throw error;
- }
- return null;
- }
- async waitForTransaction(hash, _confirms, timeout) {
- const confirms = (_confirms != null) ? _confirms : 1;
- if (confirms === 0) {
- return this.getTransactionReceipt(hash);
- }
- return new Promise(async (resolve, reject) => {
- let timer = null;
- const listener = (async (blockNumber) => {
- 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((0, index_js_6.makeError)("timeout", "TIMEOUT", { reason: "timeout" }));
- }, timeout);
- }
- listener(await this.getBlockNumber());
- });
- }
- async waitForBlock(blockTag) {
- (0, index_js_6.assert)(false, "not implemented yet", "NOT_IMPLEMENTED", {
- operation: "waitForBlock"
- });
- }
- /**
- * Clear a timer created using the [[_setTimeout]] method.
- */
- _clearTimeout(timerId) {
- 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, timeout) {
- 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) {
- for (const sub of this.#subs.values()) {
- func(sub.subscriber);
- }
- }
- /**
- * Sub-classes may override this to customize subscription
- * implementations.
- */
- _getSubscriber(sub) {
- switch (sub.type) {
- case "debug":
- case "error":
- case "network":
- return new UnmanagedSubscriber(sub.type);
- case "block": {
- const subscriber = new subscriber_polling_js_1.PollingBlockSubscriber(this);
- subscriber.pollingInterval = this.pollingInterval;
- return subscriber;
- }
- case "safe":
- case "finalized":
- return new subscriber_polling_js_1.PollingBlockTagSubscriber(this, sub.type);
- case "event":
- return new subscriber_polling_js_1.PollingEventSubscriber(this, sub.filter);
- case "transaction":
- return new subscriber_polling_js_1.PollingTransactionSubscriber(this, sub.hash);
- case "orphan":
- return new subscriber_polling_js_1.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, newSub) {
- 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, emitArgs) {
- 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) {
- 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, listener) {
- 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, listener) {
- 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, ...args) {
- 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 index_js_6.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) {
- 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) {
- if (event) {
- const sub = await this.#hasSub(event);
- if (!sub) {
- return [];
- }
- return sub.listeners.map(({ listener }) => listener);
- }
- let result = [];
- for (const { listeners } of this.#subs.values()) {
- result = result.concat(listeners.map(({ listener }) => listener));
- }
- return result;
- }
- async off(event, listener) {
- 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) {
- 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, listener) {
- return await this.on(event, listener);
- }
- // Alias for "off"
- async removeListener(event, listener) {
- 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() {
- 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() {
- // 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() { return (this.#pausedState != null); }
- set paused(pause) {
- 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) {
- this.#lastBlockNumber = -1;
- if (this.#pausedState != null) {
- if (this.#pausedState == !!dropWhilePaused) {
- return;
- }
- (0, index_js_6.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() {
- 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);
- }
- }
- }
- exports.AbstractProvider = AbstractProvider;
- function _parseString(result, start) {
- try {
- const bytes = _parseBytes(result, start);
- if (bytes) {
- return (0, index_js_6.toUtf8String)(bytes);
- }
- }
- catch (error) { }
- return null;
- }
- function _parseBytes(result, start) {
- if (result === "0x") {
- return null;
- }
- try {
- const offset = (0, index_js_6.getNumber)((0, index_js_6.dataSlice)(result, start, start + 32));
- const length = (0, index_js_6.getNumber)((0, index_js_6.dataSlice)(result, offset, offset + 32));
- return (0, index_js_6.dataSlice)(result, offset + 32, offset + 32 + length);
- }
- catch (error) { }
- return null;
- }
- function numPad(value) {
- const result = (0, index_js_6.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) {
- if ((value.length % 32) === 0) {
- return value;
- }
- const result = new Uint8Array(Math.ceil(value.length / 32) * 32);
- result.set(value);
- return result;
- }
- const empty = new Uint8Array([]);
- // ABI Encodes a series of (bytes, bytes, ...)
- function encodeBytes(datas) {
- const result = [];
- 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 = (0, index_js_6.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 (0, index_js_6.concat)(result);
- }
- const zeros = "0x0000000000000000000000000000000000000000000000000000000000000000";
- function parseOffchainLookup(data) {
- const result = {
- sender: "", urls: [], calldata: "", selector: "", extraData: "", errorArgs: []
- };
- (0, index_js_6.assert)((0, index_js_6.dataLength)(data) >= 5 * 32, "insufficient OffchainLookup data", "OFFCHAIN_FAULT", {
- reason: "insufficient OffchainLookup data"
- });
- const sender = (0, index_js_6.dataSlice)(data, 0, 32);
- (0, index_js_6.assert)((0, index_js_6.dataSlice)(sender, 0, 12) === (0, index_js_6.dataSlice)(zeros, 0, 12), "corrupt OffchainLookup sender", "OFFCHAIN_FAULT", {
- reason: "corrupt OffchainLookup sender"
- });
- result.sender = (0, index_js_6.dataSlice)(sender, 12);
- // Read the URLs from the response
- try {
- const urls = [];
- const urlsOffset = (0, index_js_6.getNumber)((0, index_js_6.dataSlice)(data, 32, 64));
- const urlsLength = (0, index_js_6.getNumber)((0, index_js_6.dataSlice)(data, urlsOffset, urlsOffset + 32));
- const urlsData = (0, index_js_6.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) {
- (0, index_js_6.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) {
- (0, index_js_6.assert)(false, "corrupt OffchainLookup calldata", "OFFCHAIN_FAULT", {
- reason: "corrupt OffchainLookup calldata"
- });
- }
- // Get the callbackSelector (bytes4)
- (0, index_js_6.assert)((0, index_js_6.dataSlice)(data, 100, 128) === (0, index_js_6.dataSlice)(zeros, 0, 28), "corrupt OffchainLookup callbaackSelector", "OFFCHAIN_FAULT", {
- reason: "corrupt OffchainLookup callbaackSelector"
- });
- result.selector = (0, index_js_6.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) {
- (0, index_js_6.assert)(false, "corrupt OffchainLookup extraData", "OFFCHAIN_FAULT", {
- reason: "corrupt OffchainLookup extraData"
- });
- }
- result.errorArgs = "sender,urls,calldata,selector,extraData".split(/,/).map((k) => result[k]);
- return result;
- }
- //# sourceMappingURL=abstract-provider.js.map
|