Socket
Socket
Sign inDemoInstall

@nomicfoundation/hardhat-ethers

Package Overview
Dependencies
Maintainers
3
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nomicfoundation/hardhat-ethers - npm Package Compare versions

Comparing version 3.0.2 to 3.0.3

4

internal/errors.d.ts

@@ -8,4 +8,4 @@ import { NomicLabsHardhatPluginError } from "hardhat/plugins";

}
export declare class NonStringEventError extends HardhatEthersError {
constructor(method: string, event: any);
export declare class UnsupportedEventError extends HardhatEthersError {
constructor(event: any);
}

@@ -12,0 +12,0 @@ export declare class AccountIndexOutOfRange extends HardhatEthersError {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BroadcastedTxDifferentHash = exports.AccountIndexOutOfRange = exports.NonStringEventError = exports.NotImplementedError = exports.HardhatEthersError = void 0;
exports.BroadcastedTxDifferentHash = exports.AccountIndexOutOfRange = exports.UnsupportedEventError = exports.NotImplementedError = exports.HardhatEthersError = void 0;
const plugins_1 = require("hardhat/plugins");

@@ -17,8 +17,8 @@ class HardhatEthersError extends plugins_1.NomicLabsHardhatPluginError {

exports.NotImplementedError = NotImplementedError;
class NonStringEventError extends HardhatEthersError {
constructor(method, event) {
super(`Method '${method}' only supports string events, got '${event}'`);
class UnsupportedEventError extends HardhatEthersError {
constructor(event) {
super(`Event '${event}' is not supported`);
}
}
exports.NonStringEventError = NonStringEventError;
exports.UnsupportedEventError = UnsupportedEventError;
class AccountIndexOutOfRange extends HardhatEthersError {

@@ -25,0 +25,0 @@ constructor(accountIndex, accountsLength) {

@@ -9,4 +9,8 @@ import type { AddressLike, BlockTag, TransactionRequest, Filter, FilterByBlockHash, Listener, ProviderEvent } from "ethers";

private _isHardhatNetworkCached;
private _latestBlockNumberPolled;
private _blockListeners;
private _transactionHashListeners;
private _eventListeners;
private _transactionHashPollingInterval;
private _blockPollingInterval;
constructor(_hardhatProvider: EthereumProvider, _networkName: string);

@@ -36,9 +40,9 @@ get provider(): this;

waitForBlock(_blockTag?: BlockTag | undefined): Promise<ethers.Block>;
on(event: ProviderEvent, listener: Listener): Promise<this>;
once(event: ProviderEvent, listener: Listener): Promise<this>;
emit(event: ProviderEvent, ...args: any[]): Promise<boolean>;
on(ethersEvent: ProviderEvent, listener: Listener): Promise<this>;
once(ethersEvent: ProviderEvent, listener: Listener): Promise<this>;
emit(ethersEvent: ProviderEvent, ...args: any[]): Promise<boolean>;
listenerCount(event?: ProviderEvent | undefined): Promise<number>;
listeners(event?: ProviderEvent | undefined): Promise<Listener[]>;
off(event: ProviderEvent, listener?: Listener | undefined): Promise<this>;
removeAllListeners(event?: ProviderEvent | undefined): Promise<this>;
listeners(ethersEvent?: ProviderEvent | undefined): Promise<Listener[]>;
off(ethersEvent: ProviderEvent, listener?: Listener | undefined): Promise<this>;
removeAllListeners(ethersEvent?: ProviderEvent | undefined): Promise<this>;
addListener(event: ProviderEvent, listener: Listener): Promise<this>;

@@ -57,6 +61,9 @@ removeListener(event: ProviderEvent, listener: Listener): Promise<this>;

private _getRpcBlockTag;
private _isHardhatNetwork;
private _onTransactionHash;
private _offTransactionHash;
private _clearTransactionHashListeners;
private _startTransactionHashPolling;
private _stopTransactionHashPolling;
private _startBlockPolling;
private _stopBlockPolling;
/**

@@ -68,5 +75,12 @@ * Traverse all the registered transaction hashes and check if they were mined.

private _pollTransactionHashes;
private _pollBlocks;
private _emitTransactionHash;
private _isHardhatNetwork;
private _emitBlock;
private _onBlock;
private _clearBlockListeners;
private _getBlockListenerForEvent;
private _addEventListener;
private _clearEventListeners;
private _removeEventListener;
}
//# sourceMappingURL=hardhat-ethers-provider.d.ts.map

@@ -17,3 +17,5 @@ "use strict";

this._networkName = _networkName;
this._blockListeners = [];
this._transactionHashListeners = new Map();
this._eventListeners = [];
}

@@ -195,138 +197,141 @@ get provider() {

}
async on(event, listener) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: false });
}
else {
this._hardhatProvider.on(event, listener);
}
// -------------------------------------- //
// event-emitter related public functions //
// -------------------------------------- //
async on(ethersEvent, listener) {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
await this._onBlock(listener, { once: false });
}
else if (event.kind === "transactionHash") {
await this._onTransactionHash(event.txHash, listener, { once: false });
}
else if (event.kind === "event") {
const { eventFilter } = event;
const blockListener = this._getBlockListenerForEvent(eventFilter, listener);
this._addEventListener(eventFilter, listener, blockListener);
await this.on("block", blockListener);
}
else {
throw new errors_1.NonStringEventError("on", event);
const _exhaustiveCheck = event;
}
return this;
}
async once(event, listener) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: true });
}
else {
this._hardhatProvider.once(event, listener);
}
async once(ethersEvent, listener) {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
await this._onBlock(listener, { once: true });
}
else if (event.kind === "transactionHash") {
await this._onTransactionHash(event.txHash, listener, { once: true });
}
else if (event.kind === "event") {
const { eventFilter } = event;
const blockListener = this._getBlockListenerForEvent(eventFilter, listener);
this._addEventListener(eventFilter, listener, blockListener);
await this.once("block", blockListener);
}
else {
throw new errors_1.NonStringEventError("once", event);
const _exhaustiveCheck = event;
}
return this;
}
async emit(event, ...args) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return this._emitTransactionHash(event, ...args);
}
else {
return this._hardhatProvider.emit(event, ...args);
}
async emit(ethersEvent, ...args) {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
return this._emitBlock(...args);
}
else if (event.kind === "transactionHash") {
return this._emitTransactionHash(event.txHash, ...args);
}
else if (event.kind === "event") {
throw new errors_1.NotImplementedError("emit(event)");
}
else {
throw new errors_1.NonStringEventError("emit", event);
const _exhaustiveCheck = event;
return _exhaustiveCheck;
}
}
async listenerCount(event) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return this._transactionHashListeners.get(event)?.length ?? 0;
const listeners = await this.listeners(event);
return listeners.length;
}
async listeners(ethersEvent) {
if (ethersEvent === undefined) {
throw new errors_1.NotImplementedError("listeners()");
}
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
return this._blockListeners.map(({ listener }) => listener);
}
else if (event.kind === "transactionHash") {
return (this._transactionHashListeners
.get(event.txHash)
?.map(({ listener }) => listener) ?? []);
}
else if (event.kind === "event") {
const isEqual = require("lodash.isequal");
const eventListener = this._eventListeners.find((item) => isEqual(item.event, event));
if (eventListener === undefined) {
return [];
}
else {
return this._hardhatProvider.listenerCount(event);
}
return [...eventListener.listenersMap.keys()];
}
else {
throw new errors_1.NonStringEventError("listenerCount", event);
const _exhaustiveCheck = event;
return _exhaustiveCheck;
}
}
async listeners(event) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return (this._transactionHashListeners
.get(event)
?.map(({ listener }) => listener) ?? []);
}
else {
return this._hardhatProvider.listeners(event);
}
async off(ethersEvent, listener) {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
this._clearBlockListeners(listener);
}
else {
throw new errors_1.NonStringEventError("listeners", event);
else if (event.kind === "transactionHash") {
this._clearTransactionHashListeners(event.txHash, listener);
}
}
async off(event, listener) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
this._offTransactionHash(event, listener);
else if (event.kind === "event") {
const { eventFilter } = event;
if (listener === undefined) {
await this._clearEventListeners(eventFilter);
}
else if (listener !== undefined) {
this._hardhatProvider.off(event, listener);
}
else {
const registeredListeners = this._hardhatProvider.listeners(event);
for (const registeredListener of registeredListeners) {
this._hardhatProvider.off(event, registeredListener);
}
await this._removeEventListener(eventFilter, listener);
}
}
else {
throw new errors_1.NonStringEventError("off", event);
const _exhaustiveCheck = event;
}
return this;
}
async removeAllListeners(event) {
if (event === undefined) {
this._hardhatProvider.removeAllListeners(event);
async removeAllListeners(ethersEvent) {
const event = ethersEvent !== undefined ? ethersToHardhatEvent(ethersEvent) : undefined;
if (event === undefined || event.kind === "block") {
this._clearBlockListeners();
}
else if (typeof event === "string") {
if (isTransactionHash(event)) {
this._transactionHashListeners.delete(event);
}
else {
this._hardhatProvider.removeAllListeners(event);
}
if (event === undefined || event.kind === "transactionHash") {
this._clearTransactionHashListeners(event?.txHash);
}
else {
throw new errors_1.NonStringEventError("removeAllListeners", event);
if (event === undefined || event.kind === "event") {
await this._clearEventListeners(event?.eventFilter);
}
if (event !== undefined &&
event.kind !== "block" &&
event.kind !== "transactionHash" &&
event.kind !== "event") {
// this check is only to remeber to add a proper if block
// in this method's implementation if we add support for a
// new kind of event
const _exhaustiveCheck = event;
}
return this;
}
async addListener(event, listener) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: false });
}
else {
this._hardhatProvider.addListener(event, listener);
}
}
else {
throw new errors_1.NonStringEventError("addListener", event);
}
return this;
return this.on(event, listener);
}
async removeListener(event, listener) {
if (typeof event === "string") {
if (isTransactionHash(event)) {
this._offTransactionHash(event, listener);
}
else {
this._hardhatProvider.removeListener(event, listener);
}
}
else {
throw new errors_1.NonStringEventError("removeListener", event);
}
return this;
return this.off(event, listener);
}
toJSON() {
return "<WrappedHardhatProvider>";
return "<EthersHardhatProvider>";
}

@@ -519,2 +524,16 @@ _getAddress(address) {

}
async _isHardhatNetwork() {
if (this._isHardhatNetworkCached === undefined) {
this._isHardhatNetworkCached = false;
try {
await this._hardhatProvider.send("hardhat_metadata");
this._isHardhatNetworkCached = true;
}
catch { }
}
return this._isHardhatNetworkCached;
}
// ------------------------------------- //
// event-emitter related private helpers //
// ------------------------------------- //
async _onTransactionHash(transactionHash, listener, { once }) {

@@ -526,4 +545,7 @@ const listeners = this._transactionHashListeners.get(transactionHash) ?? [];

}
_offTransactionHash(transactionHash, listener) {
if (listener === undefined) {
_clearTransactionHashListeners(transactionHash, listener) {
if (transactionHash === undefined) {
this._transactionHashListeners = new Map();
}
else if (listener === undefined) {
this._transactionHashListeners.delete(transactionHash);

@@ -541,7 +563,7 @@ }

}
if (this._transactionHashListeners.size === 0) {
this._stopTransactionHashPolling();
}
}
}
if (this._transactionHashListeners.size === 0) {
this._stopTransactionHashPolling();
}
}

@@ -554,2 +576,7 @@ async _startTransactionHashPolling() {

}
if (this._transactionHashListeners.size === 0) {
// it's possible that the first poll cleans all the listeners,
// in that case we don't start the interval
return;
}
if (this._transactionHashPollingInterval === undefined) {

@@ -567,2 +594,26 @@ this._transactionHashPollingInterval = setInterval(async () => {

}
async _startBlockPolling() {
const _isHardhatNetwork = await this._isHardhatNetwork();
const interval = _isHardhatNetwork ? 50 : 500;
this._latestBlockNumberPolled = await this.getBlockNumber();
if (_isHardhatNetwork) {
await this._pollBlocks();
}
if (this._blockListeners.length === 0) {
// it's possible that the first poll cleans all the listeners,
// in that case we don't start the interval
return;
}
if (this._blockPollingInterval === undefined) {
this._blockPollingInterval = setInterval(async () => {
await this._pollBlocks();
}, interval);
}
}
_stopBlockPolling() {
if (this._blockPollingInterval !== undefined) {
clearInterval(this._blockPollingInterval);
this._blockPollingInterval = undefined;
}
}
/**

@@ -588,3 +639,3 @@ * Traverse all the registered transaction hashes and check if they were mined.

for (const [transactionHash, listener] of listenersToRemove) {
this._offTransactionHash(transactionHash, listener);
this._clearTransactionHashListeners(transactionHash, listener);
}

@@ -596,2 +647,35 @@ }

}
async _pollBlocks() {
try {
const currentBlockNumber = await this.getBlockNumber();
const previousBlockNumber = this._latestBlockNumberPolled ?? 0;
if (currentBlockNumber === previousBlockNumber) {
// Don't do anything, there are no new blocks
return;
}
else if (currentBlockNumber < previousBlockNumber) {
// This can happen if there was a reset or a snapshot was reverted.
// We don't know which number the network was reset to, so we update
// the latest block number seen and do nothing else.
this._latestBlockNumberPolled = currentBlockNumber;
return;
}
this._latestBlockNumberPolled = currentBlockNumber;
for (let blockNumber = previousBlockNumber + 1; blockNumber <= this._latestBlockNumberPolled; blockNumber++) {
const listenersToRemove = [];
for (const { listener, once } of this._blockListeners) {
listener(blockNumber);
if (once) {
listenersToRemove.push(listener);
}
}
for (const listener of listenersToRemove) {
this._clearBlockListeners(listener);
}
}
}
catch (e) {
log(`Error during block polling: ${e.message}`);
}
}
_emitTransactionHash(transactionHash, ...args) {

@@ -610,17 +694,126 @@ const listeners = this._transactionHashListeners.get(transactionHash);

for (const listener of listenersToRemove) {
this._offTransactionHash(transactionHash, listener);
this._clearTransactionHashListeners(transactionHash, listener);
}
return true;
}
async _isHardhatNetwork() {
if (this._isHardhatNetworkCached === undefined) {
this._isHardhatNetworkCached = false;
try {
await this._hardhatProvider.send("hardhat_metadata");
this._isHardhatNetworkCached = true;
_emitBlock(...args) {
const listeners = this._blockListeners;
const listenersToRemove = [];
for (const { listener, once } of listeners) {
listener(...args);
if (once) {
listenersToRemove.push(listener);
}
catch { }
}
return this._isHardhatNetworkCached;
for (const listener of listenersToRemove) {
this._clearBlockListeners(listener);
}
return true;
}
async _onBlock(listener, { once }) {
const listeners = this._blockListeners;
listeners.push({ listener, once });
this._blockListeners = listeners;
await this._startBlockPolling();
}
_clearBlockListeners(listener) {
if (listener === undefined) {
this._blockListeners = [];
this._stopBlockPolling();
}
else {
const listenerIndex = this._blockListeners.findIndex((item) => item.listener === listener);
if (listenerIndex >= 0) {
this._blockListeners.splice(listenerIndex, 1);
}
if (this._blockListeners.length === 0) {
this._stopBlockPolling();
}
}
}
_getBlockListenerForEvent(event, listener) {
return async (blockNumber) => {
const eventLogs = await this.getLogs({
fromBlock: blockNumber,
toBlock: blockNumber,
});
const eventLog = eventLogs.find((e) => {
if (event.address !== undefined && e.address !== event.address) {
return false;
}
if (event.topics !== undefined) {
const topicsToMatch = event.topics;
if (e.topics.every((topic, i) => {
const topicToMatch = topicsToMatch[i];
if (topicToMatch === null) {
return true;
}
if (typeof topicToMatch === "string") {
return topic === topicsToMatch[i];
}
return topicToMatch.includes(topic);
})) {
return true;
}
}
return true;
});
if (eventLog !== undefined) {
listener(eventLog);
}
};
}
_addEventListener(event, listener, blockListener) {
const isEqual = require("lodash.isequal");
const eventListener = this._eventListeners.find((item) => isEqual(item.event, event));
if (eventListener === undefined) {
const listenersMap = new Map();
listenersMap.set(listener, blockListener);
this._eventListeners.push({ event, listenersMap });
}
else {
eventListener.listenersMap.set(listener, blockListener);
}
}
async _clearEventListeners(event) {
const isEqual = require("lodash.isequal");
const blockListenersToRemove = [];
if (event === undefined) {
for (const eventListener of this._eventListeners) {
for (const blockListener of eventListener.listenersMap.values()) {
blockListenersToRemove.push(blockListener);
}
}
this._eventListeners = [];
}
else {
const index = this._eventListeners.findIndex((item) => isEqual(item.event, event));
if (index === -1) {
const { listenersMap } = this._eventListeners[index];
this._eventListeners.splice(index, 1);
for (const blockListener of listenersMap.values()) {
blockListenersToRemove.push(blockListener);
}
}
}
for (const blockListener of blockListenersToRemove) {
await this.off("block", blockListener);
}
}
async _removeEventListener(event, listener) {
const isEqual = require("lodash.isequal");
const index = this._eventListeners.findIndex((item) => isEqual(item.event, event));
if (index === -1) {
// nothing to do
return;
}
const { listenersMap } = this._eventListeners[index];
const blockListener = listenersMap.get(listener);
listenersMap.delete(listener);
if (blockListener === undefined) {
// nothing to do
return;
}
await this.off("block", blockListener);
}
}

@@ -639,2 +832,27 @@ exports.HardhatEthersProvider = HardhatEthersProvider;

}
function isEventFilter(x) {
if (typeof x !== "string" && !Array.isArray(x) && !("orphan" in x)) {
return true;
}
return false;
}
function ethersToHardhatEvent(event) {
if (typeof event === "string") {
if (event === "block") {
return { kind: "block" };
}
else if (isTransactionHash(event)) {
return { kind: "transactionHash", txHash: event };
}
else {
throw new errors_1.UnsupportedEventError(event);
}
}
else if (isEventFilter(event)) {
return { kind: "event", eventFilter: event };
}
else {
throw new errors_1.UnsupportedEventError(event);
}
}
//# sourceMappingURL=hardhat-ethers-provider.js.map
{
"name": "@nomicfoundation/hardhat-ethers",
"version": "3.0.2",
"version": "3.0.3",
"description": "Hardhat plugin for ethers",

@@ -23,3 +23,3 @@ "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-ethers",

"prettier": "prettier \"**/*.{js,md,json}\"",
"test": "mocha --recursive \"test/**/*.ts\" --exit",
"test": "mocha --recursive \"test/**/*.ts\"",
"build": "tsc --build .",

@@ -42,3 +42,4 @@ "prepublishOnly": "yarn build",

"dependencies": {
"debug": "^4.1.1"
"debug": "^4.1.1",
"lodash.isequal": "^4.5.0"
},

@@ -48,4 +49,6 @@ "devDependencies": {

"@types/chai-as-promised": "^7.1.3",
"@types/lodash.isequal": "^4.5.6",
"@types/mocha": ">=9.1.0",
"@types/node": "^14.0.0",
"@types/sinon": "^9.0.8",
"@typescript-eslint/eslint-plugin": "5.53.0",

@@ -66,2 +69,3 @@ "@typescript-eslint/parser": "5.53.0",

"rimraf": "^3.0.2",
"sinon": "^9.0.0",
"ts-node": "^10.8.0",

@@ -68,0 +72,0 @@ "typescript": "~4.7.4"

@@ -15,5 +15,5 @@ import { NomicLabsHardhatPluginError } from "hardhat/plugins";

export class NonStringEventError extends HardhatEthersError {
constructor(method: string, event: any) {
super(`Method '${method}' only supports string events, got '${event}'`);
export class UnsupportedEventError extends HardhatEthersError {
constructor(event: any) {
super(`Event '${event}' is not supported`);
}

@@ -20,0 +20,0 @@ }

@@ -15,3 +15,5 @@ import type {

PerformActionFilter,
EventFilter,
} from "ethers";
import type LodashIsEqualT from "lodash.isequal";

@@ -34,2 +36,3 @@ import debug from "debug";

import { EthereumProvider } from "hardhat/types";
import { HardhatEthersSigner } from "../signers";

@@ -48,3 +51,3 @@ import {

HardhatEthersError,
NonStringEventError,
UnsupportedEventError,
NotImplementedError,

@@ -55,14 +58,39 @@ } from "./errors";

interface ListenerItem {
listener: Listener;
once: boolean;
}
interface EventListenerItem {
event: EventFilter;
// map from the given listener to the block listener registered for that listener
listenersMap: Map<Listener, Listener>;
}
// this type has a more explicit and type-safe list
// of the events that we support
type HardhatEthersProviderEvent =
| {
kind: "block";
}
| {
kind: "transactionHash";
txHash: string;
}
| {
kind: "event";
eventFilter: EventFilter;
};
export class HardhatEthersProvider implements ethers.Provider {
private _isHardhatNetworkCached: boolean | undefined;
private _transactionHashListeners: Map<
string,
Array<{
listener: Listener;
once: boolean;
}>
> = new Map();
// event-emitter related fields
private _latestBlockNumberPolled: number | undefined;
private _blockListeners: ListenerItem[] = [];
private _transactionHashListeners: Map<string, ListenerItem[]> = new Map();
private _eventListeners: EventListenerItem[] = [];
private _transactionHashPollingInterval: NodeJS.Timeout | undefined;
private _blockPollingInterval: NodeJS.Timeout | undefined;

@@ -352,11 +380,28 @@ constructor(

public async on(event: ProviderEvent, listener: Listener): Promise<this> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: false });
} else {
this._hardhatProvider.on(event, listener);
}
// -------------------------------------- //
// event-emitter related public functions //
// -------------------------------------- //
public async on(
ethersEvent: ProviderEvent,
listener: Listener
): Promise<this> {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
await this._onBlock(listener, { once: false });
} else if (event.kind === "transactionHash") {
await this._onTransactionHash(event.txHash, listener, { once: false });
} else if (event.kind === "event") {
const { eventFilter } = event;
const blockListener = this._getBlockListenerForEvent(
eventFilter,
listener
);
this._addEventListener(eventFilter, listener, blockListener);
await this.on("block", blockListener);
} else {
throw new NonStringEventError("on", event);
const _exhaustiveCheck: never = event;
}

@@ -367,11 +412,24 @@

public async once(event: ProviderEvent, listener: Listener): Promise<this> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: true });
} else {
this._hardhatProvider.once(event, listener);
}
public async once(
ethersEvent: ProviderEvent,
listener: Listener
): Promise<this> {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
await this._onBlock(listener, { once: true });
} else if (event.kind === "transactionHash") {
await this._onTransactionHash(event.txHash, listener, { once: true });
} else if (event.kind === "event") {
const { eventFilter } = event;
const blockListener = this._getBlockListenerForEvent(
eventFilter,
listener
);
this._addEventListener(eventFilter, listener, blockListener);
await this.once("block", blockListener);
} else {
throw new NonStringEventError("once", event);
const _exhaustiveCheck: never = event;
}

@@ -382,11 +440,17 @@

public async emit(event: ProviderEvent, ...args: any[]): Promise<boolean> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return this._emitTransactionHash(event, ...args);
} else {
return this._hardhatProvider.emit(event, ...args);
}
public async emit(
ethersEvent: ProviderEvent,
...args: any[]
): Promise<boolean> {
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
return this._emitBlock(...args);
} else if (event.kind === "transactionHash") {
return this._emitTransactionHash(event.txHash, ...args);
} else if (event.kind === "event") {
throw new NotImplementedError("emit(event)");
} else {
throw new NonStringEventError("emit", event);
const _exhaustiveCheck: never = event;
return _exhaustiveCheck;
}

@@ -398,28 +462,37 @@ }

): Promise<number> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return this._transactionHashListeners.get(event)?.length ?? 0;
} else {
return this._hardhatProvider.listenerCount(event);
}
} else {
throw new NonStringEventError("listenerCount", event);
}
const listeners = await this.listeners(event);
return listeners.length;
}
public async listeners(
event?: ProviderEvent | undefined
ethersEvent?: ProviderEvent | undefined
): Promise<Listener[]> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
return (
this._transactionHashListeners
.get(event)
?.map(({ listener }) => listener) ?? []
);
} else {
return this._hardhatProvider.listeners(event) as any;
if (ethersEvent === undefined) {
throw new NotImplementedError("listeners()");
}
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
return this._blockListeners.map(({ listener }) => listener);
} else if (event.kind === "transactionHash") {
return (
this._transactionHashListeners
.get(event.txHash)
?.map(({ listener }) => listener) ?? []
);
} else if (event.kind === "event") {
const isEqual = require("lodash.isequal") as typeof LodashIsEqualT;
const eventListener = this._eventListeners.find((item) =>
isEqual(item.event, event)
);
if (eventListener === undefined) {
return [];
}
return [...eventListener.listenersMap.keys()];
} else {
throw new NonStringEventError("listeners", event);
const _exhaustiveCheck: never = event;
return _exhaustiveCheck;
}

@@ -429,18 +502,20 @@ }

public async off(
event: ProviderEvent,
ethersEvent: ProviderEvent,
listener?: Listener | undefined
): Promise<this> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
this._offTransactionHash(event, listener);
} else if (listener !== undefined) {
this._hardhatProvider.off(event, listener);
const event = ethersToHardhatEvent(ethersEvent);
if (event.kind === "block") {
this._clearBlockListeners(listener);
} else if (event.kind === "transactionHash") {
this._clearTransactionHashListeners(event.txHash, listener);
} else if (event.kind === "event") {
const { eventFilter } = event;
if (listener === undefined) {
await this._clearEventListeners(eventFilter);
} else {
const registeredListeners = this._hardhatProvider.listeners(event);
for (const registeredListener of registeredListeners) {
this._hardhatProvider.off(event, registeredListener as any);
}
await this._removeEventListener(eventFilter, listener);
}
} else {
throw new NonStringEventError("off", event);
const _exhaustiveCheck: never = event;
}

@@ -452,16 +527,29 @@

public async removeAllListeners(
event?: ProviderEvent | undefined
ethersEvent?: ProviderEvent | undefined
): Promise<this> {
if (event === undefined) {
this._hardhatProvider.removeAllListeners(event);
} else if (typeof event === "string") {
if (isTransactionHash(event)) {
this._transactionHashListeners.delete(event);
} else {
this._hardhatProvider.removeAllListeners(event);
}
} else {
throw new NonStringEventError("removeAllListeners", event);
const event =
ethersEvent !== undefined ? ethersToHardhatEvent(ethersEvent) : undefined;
if (event === undefined || event.kind === "block") {
this._clearBlockListeners();
}
if (event === undefined || event.kind === "transactionHash") {
this._clearTransactionHashListeners(event?.txHash);
}
if (event === undefined || event.kind === "event") {
await this._clearEventListeners(event?.eventFilter);
}
if (
event !== undefined &&
event.kind !== "block" &&
event.kind !== "transactionHash" &&
event.kind !== "event"
) {
// this check is only to remeber to add a proper if block
// in this method's implementation if we add support for a
// new kind of event
const _exhaustiveCheck: never = event;
}
return this;

@@ -474,13 +562,3 @@ }

): Promise<this> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
await this._onTransactionHash(event, listener, { once: false });
} else {
this._hardhatProvider.addListener(event, listener);
}
} else {
throw new NonStringEventError("addListener", event);
}
return this;
return this.on(event, listener);
}

@@ -492,17 +570,7 @@

): Promise<this> {
if (typeof event === "string") {
if (isTransactionHash(event)) {
this._offTransactionHash(event, listener);
} else {
this._hardhatProvider.removeListener(event, listener);
}
} else {
throw new NonStringEventError("removeListener", event);
}
return this;
return this.off(event, listener);
}
public toJSON() {
return "<WrappedHardhatProvider>";
return "<EthersHardhatProvider>";
}

@@ -748,2 +816,18 @@

private async _isHardhatNetwork(): Promise<boolean> {
if (this._isHardhatNetworkCached === undefined) {
this._isHardhatNetworkCached = false;
try {
await this._hardhatProvider.send("hardhat_metadata");
this._isHardhatNetworkCached = true;
} catch {}
}
return this._isHardhatNetworkCached;
}
// ------------------------------------- //
// event-emitter related private helpers //
// ------------------------------------- //
private async _onTransactionHash(

@@ -760,7 +844,9 @@ transactionHash: string,

private _offTransactionHash(
transactionHash: string,
private _clearTransactionHashListeners(
transactionHash?: string,
listener?: Listener
): void {
if (listener === undefined) {
if (transactionHash === undefined) {
this._transactionHashListeners = new Map();
} else if (listener === undefined) {
this._transactionHashListeners.delete(transactionHash);

@@ -781,8 +867,8 @@ } else {

}
if (this._transactionHashListeners.size === 0) {
this._stopTransactionHashPolling();
}
}
}
if (this._transactionHashListeners.size === 0) {
this._stopTransactionHashPolling();
}
}

@@ -799,2 +885,8 @@

if (this._transactionHashListeners.size === 0) {
// it's possible that the first poll cleans all the listeners,
// in that case we don't start the interval
return;
}
if (this._transactionHashPollingInterval === undefined) {

@@ -814,2 +906,33 @@ this._transactionHashPollingInterval = setInterval(async () => {

private async _startBlockPolling() {
const _isHardhatNetwork = await this._isHardhatNetwork();
const interval = _isHardhatNetwork ? 50 : 500;
this._latestBlockNumberPolled = await this.getBlockNumber();
if (_isHardhatNetwork) {
await this._pollBlocks();
}
if (this._blockListeners.length === 0) {
// it's possible that the first poll cleans all the listeners,
// in that case we don't start the interval
return;
}
if (this._blockPollingInterval === undefined) {
this._blockPollingInterval = setInterval(async () => {
await this._pollBlocks();
}, interval);
}
}
private _stopBlockPolling() {
if (this._blockPollingInterval !== undefined) {
clearInterval(this._blockPollingInterval);
this._blockPollingInterval = undefined;
}
}
/**

@@ -841,3 +964,3 @@ * Traverse all the registered transaction hashes and check if they were mined.

for (const [transactionHash, listener] of listenersToRemove) {
this._offTransactionHash(transactionHash, listener);
this._clearTransactionHashListeners(transactionHash, listener);
}

@@ -849,2 +972,43 @@ } catch (e: any) {

private async _pollBlocks() {
try {
const currentBlockNumber = await this.getBlockNumber();
const previousBlockNumber = this._latestBlockNumberPolled ?? 0;
if (currentBlockNumber === previousBlockNumber) {
// Don't do anything, there are no new blocks
return;
} else if (currentBlockNumber < previousBlockNumber) {
// This can happen if there was a reset or a snapshot was reverted.
// We don't know which number the network was reset to, so we update
// the latest block number seen and do nothing else.
this._latestBlockNumberPolled = currentBlockNumber;
return;
}
this._latestBlockNumberPolled = currentBlockNumber;
for (
let blockNumber = previousBlockNumber + 1;
blockNumber <= this._latestBlockNumberPolled;
blockNumber++
) {
const listenersToRemove: Listener[] = [];
for (const { listener, once } of this._blockListeners) {
listener(blockNumber);
if (once) {
listenersToRemove.push(listener);
}
}
for (const listener of listenersToRemove) {
this._clearBlockListeners(listener);
}
}
} catch (e: any) {
log(`Error during block polling: ${e.message}`);
}
}
private _emitTransactionHash(

@@ -869,3 +1033,3 @@ transactionHash: string,

for (const listener of listenersToRemove) {
this._offTransactionHash(transactionHash, listener);
this._clearTransactionHashListeners(transactionHash, listener);
}

@@ -876,13 +1040,162 @@

private async _isHardhatNetwork(): Promise<boolean> {
if (this._isHardhatNetworkCached === undefined) {
this._isHardhatNetworkCached = false;
try {
await this._hardhatProvider.send("hardhat_metadata");
this._isHardhatNetworkCached = true;
} catch {}
private _emitBlock(...args: any[]): boolean {
const listeners = this._blockListeners;
const listenersToRemove: Listener[] = [];
for (const { listener, once } of listeners) {
listener(...args);
if (once) {
listenersToRemove.push(listener);
}
}
return this._isHardhatNetworkCached;
for (const listener of listenersToRemove) {
this._clearBlockListeners(listener);
}
return true;
}
private async _onBlock(
listener: Listener,
{ once }: { once: boolean }
): Promise<void> {
const listeners = this._blockListeners;
listeners.push({ listener, once });
this._blockListeners = listeners;
await this._startBlockPolling();
}
private _clearBlockListeners(listener?: Listener): void {
if (listener === undefined) {
this._blockListeners = [];
this._stopBlockPolling();
} else {
const listenerIndex = this._blockListeners.findIndex(
(item) => item.listener === listener
);
if (listenerIndex >= 0) {
this._blockListeners.splice(listenerIndex, 1);
}
if (this._blockListeners.length === 0) {
this._stopBlockPolling();
}
}
}
private _getBlockListenerForEvent(event: EventFilter, listener: Listener) {
return async (blockNumber: number) => {
const eventLogs = await this.getLogs({
fromBlock: blockNumber,
toBlock: blockNumber,
});
const eventLog = eventLogs.find((e) => {
if (event.address !== undefined && e.address !== event.address) {
return false;
}
if (event.topics !== undefined) {
const topicsToMatch = event.topics;
if (
e.topics.every((topic, i) => {
const topicToMatch = topicsToMatch[i];
if (topicToMatch === null) {
return true;
}
if (typeof topicToMatch === "string") {
return topic === topicsToMatch[i];
}
return topicToMatch.includes(topic);
})
) {
return true;
}
}
return true;
});
if (eventLog !== undefined) {
listener(eventLog);
}
};
}
private _addEventListener(
event: EventFilter,
listener: Listener,
blockListener: Listener
) {
const isEqual = require("lodash.isequal") as typeof LodashIsEqualT;
const eventListener = this._eventListeners.find((item) =>
isEqual(item.event, event)
);
if (eventListener === undefined) {
const listenersMap = new Map();
listenersMap.set(listener, blockListener);
this._eventListeners.push({ event, listenersMap });
} else {
eventListener.listenersMap.set(listener, blockListener);
}
}
private async _clearEventListeners(event?: EventFilter) {
const isEqual = require("lodash.isequal") as typeof LodashIsEqualT;
const blockListenersToRemove: Listener[] = [];
if (event === undefined) {
for (const eventListener of this._eventListeners) {
for (const blockListener of eventListener.listenersMap.values()) {
blockListenersToRemove.push(blockListener);
}
}
this._eventListeners = [];
} else {
const index = this._eventListeners.findIndex((item) =>
isEqual(item.event, event)
);
if (index === -1) {
const { listenersMap } = this._eventListeners[index];
this._eventListeners.splice(index, 1);
for (const blockListener of listenersMap.values()) {
blockListenersToRemove.push(blockListener);
}
}
}
for (const blockListener of blockListenersToRemove) {
await this.off("block", blockListener);
}
}
private async _removeEventListener(event: EventFilter, listener: Listener) {
const isEqual = require("lodash.isequal") as typeof LodashIsEqualT;
const index = this._eventListeners.findIndex((item) =>
isEqual(item.event, event)
);
if (index === -1) {
// nothing to do
return;
}
const { listenersMap } = this._eventListeners[index];
const blockListener = listenersMap.get(listener);
listenersMap.delete(listener);
if (blockListener === undefined) {
// nothing to do
return;
}
await this.off("block", blockListener);
}
}

@@ -903,1 +1216,27 @@

}
function isEventFilter(x: ProviderEvent): x is EventFilter {
if (typeof x !== "string" && !Array.isArray(x) && !("orphan" in x)) {
return true;
}
return false;
}
function ethersToHardhatEvent(
event: ProviderEvent
): HardhatEthersProviderEvent {
if (typeof event === "string") {
if (event === "block") {
return { kind: "block" };
} else if (isTransactionHash(event)) {
return { kind: "transactionHash", txHash: event };
} else {
throw new UnsupportedEventError(event);
}
} else if (isEventFilter(event)) {
return { kind: "event", eventFilter: event };
} else {
throw new UnsupportedEventError(event);
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc