14

My dApp have to connect to MetaMask. There are two rude solutions in the docs: make user to click connect btn every time manually or just pop up connection confirmation after page load. I want to implement the only convenient solution: first time user connect manually by clicking the connect btn and interacting with MetaMask popup and then my dApp detect that connection is still established and use this connection. I can't find the solution, but i saw this in other dApps (Capture the ether for example) I use:

import detectEthereumProvider from '@metamask/detect-provider';

const provider = await detectEthereumProvider(); 

if (provider) {
  connect(provider)
} else {
  // kind of "Install the MetaMask please!"
}

function connect(provider) {
  // How to check if the connection is here
  if (//connection established) {
    // Show the user connected account address
  } else {
    // Connect
    provider.request({ method: "eth_requestAccounts" })
      .then // some logic
  }
}
TylerH
  • 20,799
  • 66
  • 75
  • 101

6 Answers6

19

I finally found a possible solution and it turned out to be as simple as it should be. There is an eth_accounts method in Ethereum JSON-RPC which allow us to ask for available accounts without actually requesting them. This way we can check if metamask is still connected (if there are any accounts) and avoid auto requesting or need of manually clicking "connect" every time. Simple example implementation could be:

// detect provider using @metamask/detect-provider
detectEthereumProvider().then((provider) => {
  if (provider && provider.isMetaMask) {
    provider.on('accountsChanged', handleAccountsChanged);
    // connect btn is initially disabled
    $('#connect-btn').addEventListener('click', connect);
    checkConnection();
  } else {
    console.log('Please install MetaMask!');
  }
});

function connect() {
  ethereum
    .request({ method: 'eth_requestAccounts' })
    .then(handleAccountsChanged)
    .catch((err) => {
      if (err.code === 4001) {
        console.log('Please connect to MetaMask.');
      } else {
        console.error(err);
      }
    });
}

function checkConnection() {
  ethereum.request({ method: 'eth_accounts' }).then(handleAccountsChanged).catch(console.error);
}

function handleAccountsChanged(accounts) {
  console.log(accounts);

  if (accounts.length === 0) {
    $('#connection-status').innerText = "You're not connected to MetaMask";
    $('#connect-btn').disabled = false;
  } else if (accounts[0] !== currentAccount) {
    currentAccount = accounts[0];
    $('#connection-status').innerText = `Address: ${currentAccount}`;
    $('#connect-btn').disabled = true;
  }
}
GollyJer
  • 23,857
  • 16
  • 106
  • 174
3

Use window.onload to initiate the isConnected() function when the webpage is loaded. The browser console will return a wallet address if it is connected.

     window.onload = (event) => {
        isConnected();
     };
            
      async function isConnected() {
         const accounts = await ethereum.request({method: 'eth_accounts'});       
         if (accounts.length) {
            console.log(`You're connected to: ${accounts[0]}`);
         } else {
            console.log("Metamask is not connected");
         }
      }
Patrick W. McMahon
  • 3,488
  • 1
  • 20
  • 31
Mike Stratton
  • 463
  • 1
  • 7
  • 20
1

I assume you have already found Metamask docs on Ethereum Provider API. This section specifies three steps you need to do to make your app work:

  • Detect the Ethereum provider (window.ethereum)
  • Detect which Ethereum network the user is connected to
  • Get the user's Ethereum account(s)

Your snippet does the first part - it detects the provider. As per this section, to detect network you can use the following code

const chainId = await ethereum.request({ method: 'eth_chainId' });
handleChainChanged(chainId);

ethereum.on('chainChanged', handleChainChanged);

function handleChainChanged(_chainId) {
  window.location.reload();
}

And the most crucial part - fetching user account.

let currentAccount = null; 

function handleAccountsChanged(accounts) {
  if (accounts.length === 0) {
    console.log('Please connect to MetaMask.');
  } else if (accounts[0] !== currentAccount) {
    currentAccount = accounts[0];
  }
}

document.getElementById('connectButton', connect);

function connect() {
  ethereum
    .request({ method: 'eth_requestAccounts' })
    .then(handleAccountsChanged)
    .catch((err) => {
      if (err.code === 4001) {
        console.log('Please connect to MetaMask.');
      } else {
        console.error(err);
      }
    });

After the user logs in the first time, Metamask won't show the pop-up next time.

d3mage
  • 99
  • 1
  • 9
  • Thank you for the answer! So user have to click connect every time though the connection is still established (as shown in extension popup). I want to detect if metamask is already connected to the site. Yes, i can use `window.ethereum.request({ method: 'eth_requestAccounts' })`, but there is a problem for me: if metamask is not connected it will show a popup but i wanted it to happen silently. And show a popup only when user clicks "connect". – Данила Алексеев Feb 20 '22 at 13:53
  • If metamask is not connected, there's no way to connect it without showing the pop-up. Once again, check the last link, it contains EIP-1193 way of connecting website. User will have to interact with metamask the very first time he has opened the site, but the next time he will be logged in automatically – d3mage Feb 20 '22 at 19:08
  • I see what you say, but the question is a little different. If i use `window.ethereum.request({ method: 'eth_requestAccounts' })` as a button click handler then user have to hit the btn every time after page has been reloaded. If i use this method on page load then the pop up will appear without user asking for it (yes, only at the very first time). Desired behaviour is: the very first time a user hits the "connect" button and interact with a pop-up but then a user does not has to click the btn because i know (how to know is the question) that connection is still established. – Данила Алексеев Feb 20 '22 at 19:55
  • When i say "connection is still established" i mean if i request accounts then i just receive them without any user interaction. We can see connection status in the top left corner of the metamask extension window. But how we can see it in our code? – Данила Алексеев Feb 20 '22 at 20:00
  • Please, pay attention to what I'm saying. Check the link, there are two methods: one I have posted here (EIP-1102) and one that will automatically request accounts on the page loaded (EIP-1193). Requesting accounts doesn't show a pop-up if the user has already connected the account to the website. – d3mage Feb 20 '22 at 21:14
  • Both of these options lack the requested functionality, which is how do we check whether the user has already granted access to the accounts, without actually requesting access. – Jpst Sep 12 '22 at 17:12
  • @Jpst The answer is pretty simple: `eth_requestAccounts` asks the user to login if he didn't do so before or returns an address. It is written in Metamask documentation, take your time to read it :) – d3mage Sep 20 '22 at 11:23
  • @d3mage You still seem to misunderstand the wanted behaviour. Please read the actual question once more or at least my comment again. How do we check if the user has already granted access WITHOUT asking the user. If the user has NOT already granted access, then I do NOT want to request it. I just want to know that NO, the user has not granted access. I do NOT want to actually ask for permission! So no, `eth_requestAccounts` will NOT work. Please read the actual question. – Jpst Sep 21 '22 at 12:11
1

I think it's help you. In some case you noticedethereum.window.once('connect',()=>{}) is not worked and then disconnect event too.. i also face this problem and i don't know how to get userAccount address automatically after refresh so i started research on many youtube video and metamask api document. finally i got the answer.

import React, {useState,useEffect} from 'react';
 import { ethers} from 'ethers';
function App(){ 
 let [userAccount,setUserAccount] = useState({
  isConnect:false,
  Account:""
 })
 let isItConnect = async()=>{
  let provider = new ethers.providers.Web3Provider(window.ethereum);
let accounts = await provider.send("eth_requestAccounts",[]);
console.log(accounts.length)
if(accounts.length>0){
  return {
    status:true,
   userAddress:accounts[0]
  }
}
else{
  return {
    status:false,
    userAddress:""
  }
}
}
 let connect = async()=>{
let Status = await isItConnect();
 localStorage.setItem('isConnected',Status.status)
 setUserAccount((prev)=>{
return {...prev,Account:Status.userAddress}
})

}

window.ethereum.on('accountsChanged',async()=>{
localStorage.removeItem('isConnected');
setUserAccount((prev)=>{
 return {...prev,Account:""}
})
connect()
})

   useEffect(()=>{
let status = localStorage.getItem('isConnected')
if(status){
connect()
}
 if(status === null){
  if(window.ethereum.selectedAddress === null){
  console.log('welcome User!')
  }
   else{
  connect()
  }
 }
 },[])
 return  (
<>
 {userAccount.Account===""&& <button onClick={connect}>Connect Metamask!
  </button>}
   {userAccount.Account !==""&& <>
  <p>{userAccount.Account}</p>
  <p>Connected</p>
  </>
   )
 }                                
  
 
Abdul Haq
  • 9
  • 2
1

Try using window.ethereum._state.account it will show array of accounts if connected else it will show an empty array, and use the length property to further access if connected to metamask or not.

0

This would get you the wallet address. returns false if not connected.

const getAccount = async () =>  await window.ethereum.request({method: 'eth_accounts'})[0] || false;

basic call from the DOM:

window.onload = event => {
   const account = getAccount();
   console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
};  

if using react:

componentDidMount() { 
   const account = getAccount();
   console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
}
Patrick W. McMahon
  • 3,488
  • 1
  • 20
  • 31