In my web front-end, I need the ability to interact with the Ethereum blockchain.

  1. It's possible a user has MetaMask installed.
  2. It's possible a user is using the web without MetaMask installed.
  3. It's possible a user is using an old dApp browser.
  4. It's possible a user is using a modern dApp browser.

The following code takes care of all possibilities, and injects ethers.js as a last resort:

if (window.ethereum) { // Modern dapp browsers...
    window.web3 = new Web3(ethereum);
} else if (window.web3) { // Legacy dapp browsers...
    window.web3 = new Web3(web3.currentProvider);
} else { // Non-dapp browsers...
    window.web3 = ethers; // ethersjs provided library

It works! Great! I can make web3.eth.getBalance() calls and such.

Here's where it gets hairy:

It appears that the exposed interface for dealing with Contracts is not uniform across libraries, both in terms of the constructor and order of parameters.

Have I approached this entirely wrong?

  • 1
    Well... ethersjs and web3.js are obviously two different things implemented by two different groups. Why would they share the exact same API? The fact that you can set your window.web3 to both is only because Javascript is not a strong-typed language. But if you want to use both in a transparent manner, then you need to implement a wrapper with a unified API. Jun 4, 2020 at 6:44
  • @goodvibration This is the answer, as I just figured out for myself. I was expecting parity and compatibility between web3 and ethers, and that is absolutely not the case. If you repost this as response, I will mark it as accepted.
    – EvilJordan
    Jun 4, 2020 at 6:55
  • Done; see below... Jun 4, 2020 at 7:22

2 Answers 2


In your code:

window.web3 = new Web3(ethereum);
window.web3 = new Web3(web3.currentProvider);
window.web3 = ethers; // ethersjs provided library

Since web3.js and ethers.js are obviously two different modules (most likely implemented by two different groups), they have no reason to share the exact same API.

The fact that you can set your window.web3 to both is only because Javascript is a weakly-typed language, which allows you to do that at your own risk (of getting a runtime exception).

If you want to use both in a transparent manner, then you'll need to implement a wrapper with a unified API.

Some details on the difference between strongly-typed languages and weakly-typed languages:

  • A strongly-typed language has stricter typing rules at compile time, which implies that errors and exceptions are more likely to happen during compilation.
  • A weakly-typed language has looser typing rules and may produce unpredictable results or may perform implicit type conversion at runtime.

I used to use the code you are using. When you assign your provider to window.web3 things will go wrong ( I don't remember why ). But i changed my code and assigned my provider to a variable, also changed the way i check for the web3 existance and it worked without any problems:

Here is the Code:

let web3
if(typeof window !== 'undefined' && typeof window.ethereum !== 'undefined'){
  //getting Permission to access MetaMask
  web3 = new Web3(window.ethereum);

}else if (typeof window !== 'undefined' && typeof window.web3 !== 'undefined') {
  web3 = new Web3(window.web3.currentProvider);
  // In legacy MetaMasks, acccounts are always exposed

} else {
  // No Web3 detected
  const provider = new Web3.providers.HttpProvider(
  web3 = new Web3(provider);


Make sure to change the last part and replace it with your own provider.

  • Interesting... I'm trying it, but the web3.eth.Contract method is completely absent.
    – EvilJordan
    Jun 4, 2020 at 6:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.