ethers.js icon indicating copy to clipboard operation
ethers.js copied to clipboard

Add custom http.Agent / https.Agent support ( Tor, Socks5, various proxy support )

Open ayanamidev opened this issue 3 years ago • 4 comments

fixes #2709, #2775

If you want this feature before this PR is merged, there is a custom, unofficial ethers provider to use right now https://github.com/ayanamitech/ethers-axios-provider

Why adding ability to use custom http.agent / https.agent is important?

One of the missing features of ethers.js compared with web3.js is that injecting http.agent is impossible although the web3.js have been supported using custom http.agent for more than 2 years.

Example: https://web3js.readthedocs.io/en/v1.2.11/include_package-core.html?highlight=agent#configuration

@types/node definition of RequestOptions which ethers.js use for initiating remote RPC providers

https://microsoft.github.io/PowerBI-JavaScript/interfaces/node_modules__types_node_http_d.http.requestoptions.html

One of the key features of the ability to inject custom http.agent supports is that it could not only provide essential network connectivity from the censored internet environment but also secures the connection with a secure, private, and faster proxy interface without using VPN.

See also: https://www.coindesk.com/policy/2022/03/03/metamask-infura-block-certain-areas-amid-crypto-sanctions-fury/

Related issues / PR from web3.js

https://github.com/ChainSafe/web3.js/issues/887

https://github.com/ChainSafe/web3.js/issues/2827

https://github.com/ChainSafe/web3.js/issues/2946

https://github.com/ChainSafe/web3.js/pull/2980

As you could see, many chinese developers use this feature to evade network firewalls across the border.

Example use case of using web3.js with socks5 proxy ( Tor connection )

Tornado-cli ( Which supports censorship resistant, ip concealing connection with Tor Network )

https://github.com/tornadocash/tornado-cli/blob/master/cli.js#L1205

What does this PR do?

By implementing the minimal interface of http.agent which is also compatible with @types/node package ( as used by ethers.js package ), any RPC provider from ethers.js could provide remote connectivity wrapped by various type of proxies with native way without the need of routing the packets of nodejs or browser workers.

Testing this PR

This PR could be tested against various proxy agents, here is the example that connects Infura with Tor Network using the modified local built ethers.js package.

$ git clone -b custom-http-agent-support https://github.com/0xAyanami/ethers.js
$ cd ethers.js
$ npm i
$ npm i --save socks-proxy-agent
$ npm run build
$ npm i
$ nano test.js

and write this file to test.js

const { SocksProxyAgent } = require('socks-proxy-agent');
const { JsonRpcProvider } = require('./packages/providers/lib/index');

const test = async () => {
  // Infura provider URL
  const infuraUrl = "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161";
  // Use Tor Browser User Agent
  const torBrowserAgent = 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0';
  // Default tor port for Tor Browser
  const torPort = 9150;

  const ethersOptions = {
    url: infuraUrl,
    headers: { 'User-Agent': torBrowserAgent },
    agent: { https: new SocksProxyAgent('socks5h://127.0.0.1:' + torPort) }
  }

  const provider = new JsonRpcProvider(ethersOptions);
  console.log(provider.connection);
  const [ blockNumber, getBlock ] = await Promise.all([
    provider.getBlockNumber(),
    provider.getBlock('latest')
  ]);
  console.log(blockNumber);
  console.log(getBlock);
}
test();

Change the torPort value if you are using Tor with service without installing it from Tor Browser,

Run node test.js to check if it works, also try turning off the Tor to check if the SocksProxyAgent injected with ethers.js only works with tor connection only.

Example output

{
  url: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
  headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0'
  },
  agent: {
    http: SocksProxyAgent {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      timeout: null,
      maxFreeSockets: 1,
      maxSockets: 1,
      maxTotalSockets: Infinity,
      sockets: {},
      freeSockets: {},
      requests: {},
      options: {},
      lookup: false,
      proxy: [Object],
      tlsConnectionOptions: {},
      [Symbol(kCapture)]: false
    }
  }
}
6566829
{
  hash: '0xf937b8918b7f8b1241ee6e2c296a1e1f9488c105c663177ab80e3108c1ec5c1b',
  parentHash: '0x6794ae12c54db26b59bad93cd91a76d6cd5df26f9999a7e237c69485724ed46e',
  number: 6566829,
  timestamp: 1647730522,
  nonce: '0x0000000000000000',
  difficulty: 1,
  gasLimit: BigNumber { _hex: '0x01c9c364', _isBigNumber: true },
  gasUsed: BigNumber { _hex: '0x18aee5', _isBigNumber: true },
  miner: '0x0000000000000000000000000000000000000000',
  extraData: '0xd883010a0d846765746888676f312e31372e32856c696e75780000000000000035a7d2301afad39826afdab78c054d72accca8f1c9d4721d6a9848c7a34cea9065c4a37b4692623e2d2ef6a444a66573b46e4c0269c6b4b923ffcdae1af01d8a00',
  transactions: [
    '0x0d5fdd192ed877be181e38eb719b11dd6eeb1eab891d6eb6e5b77e182810d8be',
    '0xaeb30a3054e78480503620fae5290c09200c22d7be5e0f5236ff90b146ec2ae2',
    '0x7564760f0dcfeee04a9a21998cc0ea7412f664e5afaa81adafd5aa7a3f251630',
    '0x1feb97d3e025eeb1690fac0740686eb550c750533704eb19edc552e81d5ed1b2',
    '0x9dfb08e2d1638a858ae423475f09472a1a492e67a896ce29c16b8de0ed6f7a9e',
    '0x371d0b8a085f7bf7c7373b18e6f9935ba21b172183a55928ece6a6d9d7918bfe',
    '0x1779f9f8e72d1e89d80cdaa655000a2f0b52cef40db07e9ee504bd2a3195b8f4'
  ],
  baseFeePerGas: BigNumber { _hex: '0x07', _isBigNumber: true },
  _difficulty: BigNumber { _hex: '0x01', _isBigNumber: true }
}

ayanamidev avatar Mar 19 '22 22:03 ayanamidev

Hello @ayanamidev, Thanks a lot for this PR :)

I'm in need for exactly this, so I guess I won't wait for ethers maintainers to merge this - I would like to start using it right now. Not sure about how to add the HTTP agent (in my case, would be a proxy) to ethers - could you document (or link me to the documentation about) how to do so?

Thanks again!

NBMSacha avatar Jun 14 '22 12:06 NBMSacha

@NBMSacha Actually, it is hard to document how to use the feature without merging this PR since ethers.js have complicated build system. When I have time, I will code the standalone ethers.js compatible HTTP provider that supports powerful featues such as automated client-side fault tolerant load balance features, HTTP / Socks5 proxy support, etc.

ghost avatar Jun 14 '22 14:06 ghost

Thanks for your reply!! So there is no way I can get started with the codebase from your branch and use an http agent before you find some time to code the standalone version? :/

NBMSacha avatar Jun 21 '22 16:06 NBMSacha

@NBMSacha Can't tell this one would be lighter than the native ethers provider, however, if you would find some temporary workout until the changes are reflected for v6, here is something for you.

https://github.com/ayanamitech/ethers-axios-provider

ghost avatar Jul 08 '22 06:07 ghost

What happened with ethers-axios-provider repository? Was there any issue/security issue with this library?

ludekvodicka avatar Aug 25 '22 14:08 ludekvodicka

Does ethers v6 support custom http proxy now?

skyf0cker avatar May 11 '23 16:05 skyf0cker

hello, still not implemented in V6? anybody know?

cryptothink629 avatar Jul 16 '23 22:07 cryptothink629

It’s available. The Request.registerGetUrl static method can be used to replace the fetching operations used. I’m planning on getting some example up later.

ricmoo avatar Jul 16 '23 22:07 ricmoo

thank you so much! Could you show us a quick code snippet example here? Please

cryptothink629 avatar Jul 16 '23 22:07 cryptothink629

@cryptothink629 I have posted the example code which works with V6 https://github.com/ethers-io/ethers.js/discussions/4336

ghost avatar Aug 26 '23 01:08 ghost