hardhat icon indicating copy to clipboard operation
hardhat copied to clipboard

Add support for websocket connection to custom networks

Open coccoinomane opened this issue 3 years ago • 13 comments

TL;DR

Is there a specific reason why Hardhat does not support websockets to connect to custom networks? If not, I am happy to try implementing it 🙂

Context

Hello!

There are a number of cases where it is useful to listen to blockchain events, for example:

  • Token transfers
  • Pairs created on DEXes
  • Liquidity added

In these cases, a websocket connection would help to reduce both the latency and the number of node calls, which is an important factor for pay-as-you-go nodes.

However, Hardhat does not seem to support websocket connections to custom networks, as documented in this issue > https://github.com/nomiclabs/hardhat/issues/1354

I was wondering whether there was a fundamental reason for the lack of support for websocket; if not, I can try implementing it.

Cheers, Cocco

coccoinomane avatar Feb 16 '22 12:02 coccoinomane

After further inspecting the code, implementing websockets might be more complicated than what I thought at first.

I thought it would be enough to use the ethers or web3 functions for sockets, but apparently Hardhat uses its own client for requests (see method _fetchJsonRpcResponse), which relies on node-fetch which in turn does not support Websockets.

I am probably missing something here, as I was expecting Hardhat to use the same clients as Ethers/Web3.

coccoinomane avatar Feb 16 '22 15:02 coccoinomane

Websockets is something we could support but not many people have requested it (every feature comes with a maintenance cost). Since we don't have an open issue to track this feature request we could use this one to gauge interest.

Thanks for opening the request.

kanej avatar Feb 17 '22 15:02 kanej

Thank you for your reply @kanej !

In the meanwhile, to use Websockets with Hardhat, I have created a getProvider() function that returns either an HTTP provider or a Websocket provider depending on the network URL being used. It is not ideal, but here it is:

import { Provider } from "@ethersproject/providers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { URL } from "url";

/**
 * Return either an HTTP Provider or a WebSocket provider
 * depending on the network URL given to Hardhat.
 */
export function getProvider(hre: HardhatRuntimeEnvironment): Provider {
  // @ts-ignore
  const url = new URL(hre.network.config.url);
  switch (url.protocol) {
    case "http:":
    case "https:":
      return new hre.ethers.providers.JsonRpcProvider(url.href);
    case "ws:":
    case "wss:":
      return new hre.ethers.providers.WebSocketProvider(url.href);
    default:
      throw new Error(`Network URL not valid: '${url.href}'`);
  }
}

Hopefully it can help fellow coders trying to use Websockets with Hardhat :-) Cheers, Cocco

coccoinomane avatar Feb 18 '22 15:02 coccoinomane

This issue was marked as stale because it didn't have any activity in the last 30 days. If you think it's still relevant, please leave a comment indicating so. Otherwise, it will be closed in 7 days.

github-actions[bot] avatar May 05 '22 23:05 github-actions[bot]

Added keep-alive support, using the approach described on this ethers.js issue.

(Please note that the getProvider() function is defined in https://github.com/NomicFoundation/hardhat/issues/2391#issuecomment-1044673909.)

import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { WebSocketProvider } from '@ethersproject/providers';

/**
 * Start a keep-alive WebSocket connection in Hardhat.
 * 
 * The function will periodically check whether the connection is still
 * open, and restart it if it is not.
 *
 * Usage:
 *
 *   startConnection(hre, async (hre, provider) => {
 *       // Your code here
 *   }
 *
 * Source: https://github.com/ethers-io/ethers.js/issues/1053#issuecomment-808736570
 */
export function startConnection(
  hre: HardhatRuntimeEnvironment,
  onOpen: (hre: HardhatRuntimeEnvironment, p: WebSocketProvider) => void,
  expectedPongBack: number = 15000,
  keepAliveCheckInterval: number = 7500
): void {
  const logger = new hre.ethers.utils.Logger('v1.0');
  const provider: WebSocketProvider = getProvider(hre) as WebSocketProvider;

  let pingTimeout: NodeJS.Timeout;
  let keepAliveInterval: NodeJS.Timeout;

  provider._websocket.on('open', () => {
    keepAliveInterval = setInterval(() => {
      logger.debug('> Checking if the connection is alive, sending a ping');
      provider._websocket.ping();
      // Delay should be equal to the interval at which your server
      // sends out pings plus a conservative assumption of the latency.
      pingTimeout = setTimeout(() => {
        provider._websocket.terminate();
      }, expectedPongBack);
    }, keepAliveCheckInterval);

    onOpen(hre, provider);
  });

  provider._websocket.on('close', () => {
    logger.warn('> WARNING: The websocket connection was closed');
    clearInterval(keepAliveInterval);
    clearTimeout(pingTimeout);
    startConnection(hre, onOpen);
  });

  provider._websocket.on('pong', () => {
    logger.debug('> Received pong, so connection is alive, clearing the timeout');
    clearInterval(pingTimeout);
  });
}

coccoinomane avatar May 06 '22 02:05 coccoinomane

This issue was marked as stale because it didn't have any activity in the last 30 days. If you think it's still relevant, please leave a comment indicating so. Otherwise, it will be closed in 7 days.

Would it be possible to leave the issue open?

Although not overwhelming, there has been some interest in having the feature implemented (see reactions).

Cheers, Cocco

coccoinomane avatar May 06 '22 09:05 coccoinomane

Marked as not-stale so the bot doesn't comment again.

fvictorio avatar May 12 '22 14:05 fvictorio

Added keep-alive support, using the approach described on this ethers.js issue:

This function is great but we would need to re-subscribe to the events on the new connection, There was an example for using web3 over on the other thread but it didn't cover ethers.js

iangregsondev avatar Jul 31 '22 23:07 iangregsondev

Hi all,

Here is a plugin that adds the getProvider function to hre.

In case that you use a network that it is configured to works with web sockets, returns an instance of WebSocketProvider, in other cases, returns hre.ethers.provider.

Here is the npm package:

https://www.npmjs.com/package/@sebasgoldberg/hardhat-wsprovider

In the future I am planning to implement automatic re-subscription.

sebasgoldberg avatar Aug 12 '22 21:08 sebasgoldberg

@sebasgoldberg thank you this is a huge help!!

sschepis avatar Oct 17 '22 04:10 sschepis

really an important feature to be added! WS is must novadays

makinghappen avatar Mar 28 '23 18:03 makinghappen

+1 Would really like to be able to use a mainnet fork with websockets.

arondius avatar Feb 11 '24 12:02 arondius

+1

Nasfame avatar Feb 15 '24 14:02 Nasfame