16

Context: I want to use blockies to render an identicon on the page, i get the defaultAccount from web3, for this, the user has to be logged on to metamask with a selected address from his wallet.

The problem: the web app seems to not detect the web3 object on the load event of the page, wchih is the recommended place to detect it.

The code: below is inspired from recommendations at:

https://github.com/MetaMask/metamask-plugin/issues/1158

https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#partly_sunny-web3---ethereum-browser-environment-check

I keep having intermittent behaviour, sometimes web3 is there and sometimes it is not, the only solution I can think of is to have a timer, but that seems to me a bit too simplistic, I would prefer something more elegant.

Question: Is there a better solution to detect the defaultAccount from web3 when the page loads?

 function startApp() { 
        GenerateIdenticon();  
}  


window.addEventListener('load', function () { 

// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {

    // Use Mist/MetaMask's provider
    window.web3 = new Web3(web3.currentProvider); 
    if (web3.currentProvider.isMetaMask === true) {
        if (typeof web3.eth.defaultAccount === 'undefined') {
            document.body.innerHTML = '<body><h1>Oops! Your browser does not support Ethereum Ðapps.</h1></body>';   
        }
        else {
            startApp();
        }
    }
    else {
         alert('No web3? Please use google chrome and metamask plugin to enter this Dapp!', null, null);
        // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
       window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

6 Answers 6

17

The one method that is more reliable than the in-page accounts array is web3.eth.getAccounts(accounts => console.log(accounts[0])).

This will asynchronously request the accounts array, and call back whenever it's available.

6
  • Thanks a lot DanF, indeed using this method always returns the desirable state. Commented Jun 9, 2017 at 7:51
  • 1
    Alternative: web3.eth.getAccounts().then(...). Commented Oct 12, 2017 at 16:44
  • Since I couldn't get this work, I discover that getAccounts method returns two (2) arguments and the accounts is the second. In other words: web3.eth.getAccounts((error, accounts) => console.log(accounts[0])).
    – Jobsamuel
    Commented Mar 1, 2018 at 18:47
  • Does that have an risk to flood the provider if it requests that information each 100 seconds ? Commented Apr 4, 2018 at 15:00
  • This is not a severe risk, because the account provider is local, so this is not a network call, just a value check on the same machine.
    – DanF
    Commented Apr 8, 2018 at 19:13
10

Here's what I use in my Dapp. It seems to work pretty well.

function getWeb3(callback) {
  if (typeof window.web3 === 'undefined') {
    // no web3, use fallback
    console.error("Please use a web3 browser");
  } else {
    // window.web3 == web3 most of the time. Don't override the provided,
    // web3, just wrap it in your Web3.
    var myWeb3 = new Web3(window.web3.currentProvider); 

    // the default account doesn't seem to be persisted, copy it to our
    // new instance
    myWeb3.eth.defaultAccount = window.web3.eth.defaultAccount;

    callback(myWeb3);
  }
}

Used like so:

function startApp(web3) {
  // ...
}

window.addEventListener('load', function() {
  getWeb3(startApp);
});

The key difference is that I copy the defaultAccount from the original window.web3 instance. You'll notice that there is no MetaMask specific code web3.eth.defaultAccount is part of the Javascript API. MetaMask seems to populate defaultAccount once you unlock your wallet.

1
  • thanks @0xcaff but as DeviateFish mentioned, you still have the same problem with the defaultAccounts, anyways I think you have a point about not having any code related to metamask and just rely on the standard web3 calls. Commented Jun 9, 2017 at 7:47
6

Checking web3.eth.accounts[0] every 100 milliseconds is way faster in some cases than web3.eth.getAccounts():

// Option 1:
web3.eth.getAccounts(console.log);

// Option 2:
(function loop() {
  if (web3.eth.accounts[0]) {
    console.log(web3.eth.accounts[0]);
  } else {
    setTimeout(loop, 100);
  }
}());

When using a proxy, Option 2 was up to 35 seconds (!) faster for me than Option 1.


Regarding web3.eth.defaultAccount, MetaMask Documentation says it should not be used to detect what account is currently selected by the user.


Btw, according to MetaMask Documentation, you will have to use setTimeout/setInterval anyway to listen for selected account changes:

var account = web3.eth.accounts[0];
setInterval(function () {
  if (web3.eth.accounts[0] !== account) {
    account = web3.eth.accounts[0];
    updateInterface();
  }
}, 100);
4

A couple things I've noticed from using MetaMask.

  1. defaultAccount isn't always populated. It's safer to use web3.eth.accounts[0] as your check to see whether or not an account is selected. You'll need to poll this value periodically if you want to know if the selected account changes, as well.

  2. The load event doesn't always seem reliable in some of my experiences, either. I eventually resolved this, but I don't have my code on hand to demonstrate how. I'll update this post with it later, if the accounts[0] piece isn't enough.

2
  • thanks @DeviateFish indeed I could not make sure the defaultAccount was populated everytime, but DanF recommended to use getAccounts with a callback function. Also for the change of accounts, it would be nice to have a watch callback whenever the account changes don't you think? Commented Jun 9, 2017 at 7:48
  • It indeed would be nice, but such a thing currently doesn't exist in web3 (or via the RPC, as far as I can tell). Using the async methods are generally preferable, so I'm glad that's working for you! Commented Jun 9, 2017 at 20:47
1

If your are using React and you are facing this problem, you only need to do this:

1. Install web3:

npm install web3 --save

2. Check if MetaMask is there:

import 'Web3' from 'web3';

// ...

componentDidMount() {

  // Check if Web3 has been injected by the browser (MetaMask).
  // (since 'web3' is global, we need to use 'window')
  if (window.web3 && window.web3.currentProvider.isMetaMask) {
    window.web3.eth.getAccounts((error, accounts) => {

      // Do whatever you need to.
      this.setState({wallet: accounts[0]});
    });
  } else {
    console.log('MetaMask account not detected :(');
  }
}
5
  • My currentProvider property returns as null at this point. Is there a callback or something I can use when it completes?
    – user339946
    Commented Mar 13, 2018 at 3:30
  • @user339946 does return null when you unlocked Metamask?
    – Jobsamuel
    Commented Mar 13, 2018 at 14:28
  • Yeah, metamask is unlocked. It seems that the web3 object isn't ready for the component at this point. Any ideas?
    – user339946
    Commented Mar 13, 2018 at 17:58
  • 1
    How do you check for mobile wallets like Trust or Status? I can catch metamask simply but none of the mobile wallets.
    – Chris
    Commented Mar 1, 2019 at 23:06
  • I don't know @JamboDev :(
    – Jobsamuel
    Commented Mar 4, 2019 at 16:22
-1

Not a shameless plug.

Recently started using: https://www.blocknative.com/

They offer good onboarding patterns with MetaMask.

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.