Mejor patrón para detectar la cuenta predeterminada de web3 cuando se usa metamask

Contexto: quiero usar bloques para representar un identicon en la página, obtengo la cuenta predeterminada de web3, para esto, el usuario debe iniciar sesión en metamask con una dirección seleccionada de su billetera.

El problema: la aplicación web parece no detectar el objeto web3 en el evento de carga de la página, que es el lugar recomendado para detectarlo.

El código: a continuación está inspirado en las recomendaciones de:

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

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

Sigo teniendo un comportamiento intermitente, a veces web3 está ahí ya veces no, la única solución que se me ocurre es tener un temporizador, pero eso me parece un poco simplista, preferiría algo más elegante.

Pregunta: ¿Existe una solución mejor para detectar la cuenta predeterminada de web3 cuando se carga la página?

 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"));
}

Respuestas (6)

El único método que es más confiable que la accountsmatriz en la página es web3.eth.getAccounts(accounts => console.log(accounts[0])).

Esto solicitará de forma asincrónica la matriz de cuentas y devolverá la llamada cuando esté disponible.

Muchas gracias DanF, de hecho, usar este método siempre devuelve el estado deseado.
Alternativa: web3.eth.getAccounts().then(...).
Como no pude obtener este trabajo, descubrí que el getAccountsmétodo devuelve dos (2) argumentos y accountses el segundo. En otras palabras: web3.eth.getAccounts((error, accounts) => console.log(accounts[0])).
¿Tiene eso un riesgo de inundar al proveedor si solicita esa información cada 100 segundos?
Este no es un riesgo grave, porque el proveedor de la cuenta es local, por lo que no se trata de una llamada de red, solo de una verificación de valor en la misma máquina.
la consola de Chrome muestra un valor "indefinido"

Esto es lo que uso en mi Dapp. Parece funcionar bastante bien.

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);
  }
}

Usado así:

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

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

La diferencia clave es que copio el de la instancia defaultAccountoriginal . window.web3Notarás que no hay un código específico de MetaMask que web3.eth.defaultAccountsea parte de la API de Javascript. MetaMask parece llenarse defaultAccountuna vez que desbloqueas tu billetera.

gracias @0xcaff pero como mencionó DeviateFish, todavía tiene el mismo problema con las cuentas predeterminadas, de todos modos, creo que tiene razón sobre no tener ningún código relacionado con metamask y solo confía en las llamadas web3 estándar.

Verificar web3.eth.accounts[0]cada 100 milisegundos es mucho más rápido en algunos casos que 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);
  }
}());

Al usar un proxy, Option 2fue hasta 35 segundos (!) Más rápido para mí que Option 1.


Con respecto a web3.eth.defaultAccount, la documentación de MetaMask dice que no debe usarse para detectar qué cuenta está seleccionada actualmente por el usuario.


Por cierto, de acuerdo con la documentación de MetaMask, deberá usar setTimeout/ setIntervalde todos modos para escuchar los cambios de cuenta seleccionados :

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

Un par de cosas que he notado al usar MetaMask.

  1. defaultAccountno siempre está poblado. Es más seguro usarlo web3.eth.accounts[0]como verificación para ver si una cuenta está seleccionada o no. Deberá sondear este valor periódicamente si desea saber si la cuenta seleccionada también cambia.

  2. El loadevento tampoco siempre parece confiable en algunas de mis experiencias. Eventualmente resolví esto, pero no tengo mi código a mano para demostrar cómo. Actualizaré esta publicación más tarde, si la accounts[0]pieza no es suficiente.

gracias @DeviateFish, de hecho, no pude asegurarme de que la cuenta predeterminada se completara cada vez, pero DanF recomendó usar getAccounts con una función de devolución de llamada. También para el cambio de cuentas, sería bueno tener una devolución de llamada cuando cambie la cuenta, ¿no crees?
De hecho, sería bueno, pero tal cosa actualmente no existe en web3 (o a través del RPC, por lo que puedo decir). En general, es preferible usar los métodos asíncronos, ¡así que me alegro de que te funcione!

Si está utilizando React y enfrenta este problema, solo necesita hacer esto:

1. Instalar web3:

npm install web3 --save

2. Compruebe si MetaMask está allí:

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 :(');
  }
}
Mi propiedad currentProvider regresa como nula en este punto. ¿Hay una devolución de llamada o algo que pueda usar cuando se complete?
@user339946 regresa nullcuando desbloqueaste Metamask?
Sí, la metamáscara está desbloqueada. Parece que el objeto web3 no está listo para el componente en este momento. ¿Algunas ideas?
¿Cómo verifica las billeteras móviles como Trust o Status? Puedo atrapar metamask simplemente pero ninguna de las billeteras móviles.
No conozco a @JamboDev :(

No es un enchufe desvergonzado.

Recientemente comencé a usar: https://www.blocknative.com/

Ofrecen buenos patrones de incorporación con MetaMask.