Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow dapp to disconnect accounts #8990

Closed
pi0neerpat opened this issue Jul 14, 2020 · 57 comments
Closed

Allow dapp to disconnect accounts #8990

pi0neerpat opened this issue Jul 14, 2020 · 57 comments

Comments

@pi0neerpat
Copy link

pi0neerpat commented Jul 14, 2020

What problem are you trying to solve?

With the new updates to v8, it is important for the dapp developer to be able to disconnect an account. Otherwise, there is no way to handle account mismatch scenarios. Also without it, the user must manually disconnect their account in the MetaMask settings, which is a poor user experience.

Describe the solution you'd like

An Ethereum Provider API like ethereum.disconnect() or ethereum.close()

Additional context

Related to #8956 . This would fall under the proposed Hard mode developer experience. This functionality is vital to being able to provide a streamlined experience for our dapp.

WalletConnect and WalletLink both implement this functionality.

@pi0neerpat pi0neerpat changed the title Allow dapp disconnect accounts Allow dapp to disconnect accounts Jul 14, 2020
@danfinlay
Copy link
Contributor

There are probably other ways of handling the account mismatch scenarios that you're describing. Could you give us a concrete example that we could use to work on?

@pi0neerpat
Copy link
Author

pi0neerpat commented Jul 15, 2020

Ok. The account mismatch scenario is not actually my issue, it was just an example.

My actual issue is the second example. I have a "disconnect" button, which will log a user out, so they can log in again with a different wallet. My dapp is designed for using 1 wallet at a time, in order to simplify things.

Megan has logged in using MetaMask account1. She now wants to use MetaMask account2. She clicks the Disconnect button, and returns to the login page. When she selects MetaMask, the dapp calls .enable() however she is not prompted to select a different account. She has no other option than to go into her MetaMask settings and manually disconnect the dapp.

If I could call disconnect with the Ethereum provider API, this would allow Megan to go through the initial .enable() flow again, without needing to use the MetaMask settings.

@danfinlay
Copy link
Contributor

This is similar to Roman’s request, wanting a way to re-prompt the user for possibly different accounts. I believe you can get this result today by either using the wallet_requestAccounts method or wallet_requestPermissions for the eth_accounts permission. I will verify this in the morning, need to do dinner now.

@danfinlay
Copy link
Contributor

One thing you could do also is to just use disconnect to represent local app state, and if a user disconnects again and then connects again and selects MetaMask, use the existing permission to resume the user’s session with no prompt. If the user switches account, they will be prompted within MetaMask to connect if they want, and your app can refer to accounts[0] as their most recently selected account.

@Gudahtt
Copy link
Member

Gudahtt commented Jul 15, 2020

When she selects MetaMask, the dapp calls .enable() however she is not prompted to select a different account. She has no other option than to go into her MetaMask settings and manually disconnect the dapp.

She does have another option; switching accounts within MetaMask. The MetaMask UI has always been the sole method of switching between accounts. If a user wants to switch accounts on a dapp, they use the MetaMask account menu. If a dapp wants the user to switch accounts, they ask the user to switch accounts within MetaMask (using the account menu). This was true before v8 as well.

Does that handle your scenario adequately? Or is there some other scenario that warrants this disconnect feature?

@pi0neerpat
Copy link
Author

a way to re-prompt the user for possibly different accounts.

This would be great. Thanks I'll try out your wallet_requestAccounts suggestion and see if that will suffice.

Thanks @Gudahtt . I would really like the behavior to work the same for all wallet types (WalletConnect, WalletLink). So disconnect should actually feel like a disconnect, and it should do what the user expects.

I don't think that just keeping it the way it has always been is a good argument. I know MetaMask is working hard to update UX after feedback, but I also think I should have the power to use it however I want to provide the best experience for my users. If I can provide a better experience in my dapp for the user, then I should be able to. Asking the user to interact with their metamask settings for something so simple is a bit of a backwards step.

@Gudahtt
Copy link
Member

Gudahtt commented Jul 15, 2020

I didn't mean to suggest that allowing dapps to disconnect was a bad idea necessarily. I would just like to understand the motivation. I doubt we're likely to design any such method well without understanding the use cases well.

I'm still having trouble understanding why a dapp would want to encourage a user to disconnect. The initial connection signifies that the user trusts the dapp enough to see their account(s), so I can see the user wanting to disconnect if they lost trust somehow. Or maybe the user doesn't intended to use the dapp anymore, and wants to disconnect to de-clutter their wallet permissions. But neither of those are of concern to a dapp.

@pi0neerpat
Copy link
Author

I'm not encouraging disconnection. I'm just trying to provide the simplest, easiest interface for managing multiple accounts. Logging in/out of the app is the most familiar "web2" method to achieve this. I like the KISS way of doing things.

I could add a switching account feature like Google does, but I think that's overkill, and would unnecessarily complicate an otherwise straightforward app. It would require much more design and development cycles as well

@danfinlay
Copy link
Contributor

This would be great. Thanks I'll try out your wallet_requestAccounts suggestion and see if that will suffice.

On a second look, it turns out the method you want is wallet_requestPermissions to re-prompt regardless of previously granted permissions.

So:

const permissions = await ethereum.request({
  method: 'wallet_requestPermissions',
  params: [{
    eth_accounts: {},
  }]
});

@pi0neerpat
Copy link
Author

Trying now. will report back and close this issue if I am successful :)

@pi0neerpat
Copy link
Author

pi0neerpat commented Jul 15, 2020

I tried both eth_requestAccounts and wallet_requestPermissions

Neither will trigger the prompt again.

UPDATE: Scratch that. wallet_requestPermissions is working now

@pi0neerpat
Copy link
Author

pi0neerpat commented Jul 15, 2020

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

@jaymutzafi
Copy link

In case it's useful, I landed here looking for a way to create a disconnect button. The use case is that some users might expect this, especially if connecting the wallet replaces an account login. Yes, many know that you can do this from the wallet itself, but it's not the common convention.

@pi0neerpat
Copy link
Author

@jaymutzafi yes, this is exactly what I was able to accomplish using this code #8990 (comment)

You can try it yourself here https://spendless.io

@Kalmbik61
Copy link

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

I am don't understand.... HOW U DO THAT, BRO ?
How i can disconnected all my metamask wallets if I was click on disconnect button ?

@jackey
Copy link

jackey commented May 12, 2021

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

i also still not get how to do that ?

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

this code makes disconnect happened ?

@Kalmbik61
Copy link

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

i also still not get how to do that ?

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

this code makes disconnect happened ?

This code is disconnecting from metamask only 1 profile if user have more then one of it...
I wanna disconnecting all clients profiles.
Now, I am disconnecting only on client

@jackey
Copy link

jackey commented May 13, 2021

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

i also still not get how to do that ?

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

this code makes disconnect happened ?

This code is disconnecting from metamask only 1 profile if user have more then one of it...
I wanna disconnecting all clients profiles.
Now, I am disconnecting only on client

 await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });

this code doesn't disconnect from metamask ? but gives a popup a confirm account list . did i something wrong ?

image

@jackey
Copy link

jackey commented May 13, 2021

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

please help, my code is just like your pasted here .But it works difference and open a confirm account popup as like login metamask does.

DAPPContract.prototype.requestMetamaskAccount = function () {
    return this.ethereum.request({
        method: 'eth_requestAccounts',
        params: [{
            eth_accounts: {}
        }]
    });
}
DAPPContract.prototype.disconnect = async function () {
    await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [{
            eth_accounts: {}
        }]
    });
}

@Kalmbik61
Copy link

Great success! Pasting code here for others.

    const walletAddress = await window.ethereum.request({
      method: "eth_requestAccounts",
      params: [
        {
          eth_accounts: {}
        }
      ]
    });

    if (!isReturningUser) {
    // Runs only they are brand new, or have hit the disconnect button
      await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [
          {
            eth_accounts: {}
          }
        ]
      });
    }

metamask flow

please help, my code is just like your pasted here .But it works difference and open a confirm account popup as like login metamask does.

DAPPContract.prototype.requestMetamaskAccount = function () {
    return this.ethereum.request({
        method: 'eth_requestAccounts',
        params: [{
            eth_accounts: {}
        }]
    });
}
DAPPContract.prototype.disconnect = async function () {
    await window.ethereum.request({
        method: "wallet_requestPermissions",
        params: [{
            eth_accounts: {}
        }]
    });
}

Yes, you are right! This code calls a popup from metamask. And if you are clicking on any profile to LOGIN - you are disconnecting first profile from website. I don't know how it works ....
Maybe on next release we can disconnecting from metamask profiles =)

@Shekhar7860
Copy link

@pi0neerpat, i am unable logout/disconnect) from metamask in react.js, plz help me

@ddon
Copy link

ddon commented Jul 29, 2021

checked spendless.io and disconnect there works as supposed to, without showing metamask popup again, and somehow opensea also knows how to disconnect. has anybody cracked this? :)

funny that issue is closed, like it was resolved, but it was not :)

@pi0neerpat
Copy link
Author

@ddon While I totally feel your pain, its not really an issue with MetaMask, but rather your implementation of it. You essentially have to keep track of some state (connected/no connected) in your app. I do this with local storage. If the user disconnects, then I call different functions the next time they hit the connect button. Check out the code I posted again and hopefully this will make it more clear.

@eezdev
Copy link

eezdev commented Feb 13, 2022

const accounts = await window.ethereum.request({
    method: "wallet_requestPermissions",
    params: [{
        eth_accounts: {}
    }]
}).then(() => ethereum.request({
    method: 'eth_requestAccounts'
}))

const account = accounts[0]

There is no need to call 'wallet_requestPermissions' before 'eth_requestAccounts' because the former is called under the hood for the 'eth_accounts' permission when calling the latter, check out the doc:
https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts

document.getElementById('connectButton', connect);

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

@metayash
Copy link

metayash commented Feb 16, 2022

I have read the thread and didn't see any approach to actually disconnect metamask, as the dapp could always call eth_requestAccounts if the browser is kept open

  • what should the disconnect button on a dapp actually call?
  • is there a way a dapp no longer can have direct access to accounts without user manually doing a metamask "disconnect" ?

@ghost
Copy link

ghost commented Mar 15, 2022

@jaymutzafi yes, this is exactly what I was able to accomplish using this code #8990 (comment)

You can try it yourself here https://spendless.io

https://spendless.io doesn't disconnect account on disconnect button click. It just updates UI, but you can see your wallet is still connected inside Metamask extension. Also the code examples with requestPermissions and requestAccounts just open Metamask modal and allow only disconnect 2nd, 3rd, etc accounts, but one account will always connected.

@pi0neerpat
Copy link
Author

you can see your wallet is still connected inside Metamask extension

Who cares? There's no absolutely no reason to do this.

It seems many people here are searching for a solution to a problem that doesn't actually exist. The purpose of this issue was to find a good UX experience to allow the user to connect a different account. We solved it. After disconnect, the next time they hit connect they can select a different account. This is the best possible experience. Anything beyond that is a waste of time.

image

@romeldris
Copy link

Hi @pi0neerpat I'm currently working on a dapp that requires both metamask and coinbase wallet support like you have on spendless.io. One thing that I have noticed is that if you switch accounts between coinbase/metamask on any subsequent connect it always picks up the coinbase wallet address even if you select Metamask. I'm thinking that most users won't ever connect with both wallets but this is a pretty weird edge case that I haven't been able to solve and wondering if this is. something you've encountered.

Here's a video of the reproducing my findings on spendless.io:

multiwallet.mov

@rolandm2017
Copy link

So is there a way to request Metamask disconnects itself from the website or not? Its not clear from reading the thread. I tested the method: "wallet_requestPermissions", params: [{ eth_accounts: {} }] thing. It did not enable me to disconnect a specific account with a known public address like I wanted to.

Seems like a missing functionality. Why wouldn't a website want to offer a user a simple button to press when they want to disconnect their wallet from the site? "Power users know how to do it from inside the wallet" isn't a counterargument. It should be exposed to the website.

provider.request({ method: "eth_disconnect"}, [accountAddress]) would suffice.

@pi0neerpat
Copy link
Author

@Plutownium

There is no such thing as a wallet "disconnect". Once a dapp knows the wallet address, they have this information forever. Unlike an OAuth2 connection, there is no way to "revoke/disconnect" access to the user's information, since this information is on-chain. "Disconnect" is just syntactic sugar to describe UX for "user intends to connect a different wallet".

Crypto is about control. We must be aware of what information we are gathering from the user (IP address, cookies, wallet address, etc.), and be careful what we do with it.

@romeldris thanks! Looks like a bug to me.

@renra
Copy link

renra commented Jun 14, 2022

@pi0neerpat

I think that's not the point. The purpose here seems to be that we only need the same behaviour one gets from disconnecting from MetaMask, but we need to trigger it from a connected site. I've tried provider.request({ method: "eth_requestPermissions"}, [eth_accounts: {}]) as suggested here. It appears to work but when I reload the site and make it query the accounts then I am connected again, because metamask is still connected to the site. So it's not a real solution.

@duck-dev-go
Copy link

+1

@efstajas
Copy link

efstajas commented Sep 14, 2022

Sorry for piling on this closed issue, but just want to add my two cents.

@Gudahtt:

It seems that people are comparing "connect" and "disconnect" to "login" and "logout", but they're fundamentally different. Logging in is the process of identifying yourself to a system, and then logout is a way to signify that you no longer want to assume that identity. But connecting does not prove anything about your identity. By connecting you expose one or more accounts, but you don't prove that you own them (they can be trivially spoofed in the dev console, by another extension, etc.).

This is obviously 100% accurate, but in my experience, it's not how the average user understands the process of connecting — and that is ultimately the only thing that matters.

Connecting & disconnecting feels so similar to logging in & out of a site via an oAuth flow (which everyone has done a million times by now) that I really doubt most people are aware of the nuance.

Given that:

We didn't think to provide a "Disconnect" method, because why would a site want to declare itself as untrustworthy?

Why would a web2 site offer a logout button? Because the user has finished what they came for with that particular account, and now either want to end the session to force an explicit login the next time they visit for security reasons, or switch to a different account.

In order to make my users (especially those not well-versed with web3) feel as at-home as possible, I would like to avoid the additional mental overhead of making them understand the nuance of connecting & disconnecting vs. what they're used to — logging in & out of a site. If I don't have a "disconnect" / "log-out" button on my site, I bet a good number of users would go searching for it, unaware that they need to disconnect from within their wallet app, because that's not what they used to with classic oAuth logins.

Additionally, while of course I as the developer don't want to declare my own site as "untrustworthy", my users may not want to stay connected to my site, and prefer disconnecting. Why shouldn't I be able to make this convenient for them as a feature?

Lastly, connecting a wallet is obviously NOT akin to authenticating, but if my app requires a signing challenge for a real log-in, the situation becomes even worse. Now, my user can log in to my site by connecting a wallet and then signing a message. And they can also log-out (in the classic web2 sense) by clicking "Log out". But after doing this, their wallet stays "connected" — which I'd bet is not at all what the average user would expect. After all, they just logged in by connecting their wallet and confirming a signature, so why would logging out not undo the connection too?

@roninjin10
Copy link
Contributor

@danfinlay I think there is a good reason for I as a user to want this disconnect functionality. I completely agree it makes no sense to use the UI to disconnect if you don't trust the UI. But if I connected, that means I DO trust the UI. What I don't trust is the UI 1 month from now, or 1 year for now.

I have seen users use disconnect in the past thinking they were being prudent in this way. They were unaware that applications are simply just simulating a disconnect and that their wallet is still connected. To me this is a problem and we should instead be training the users to expect something resembling a confirmation that they are indeed disconnected from metamask.

@luca992
Copy link

luca992 commented Oct 12, 2022

@vincenzor summed this up pretty well

Currently building a dAPP and I'm struggling to create a simple "Disconnect" button. How does something so obvious need to be discussed so much??

@pi0neerpat can you reopen this? What does it take to get an an actual @MetaMask dev to reply....

@MilanObrenovic
Copy link

I'm unable to disconnect from metamask. Tried all the examples people commented here but the program opens the metamask to connect instead of disconnecting.

@cpor3
Copy link

cpor3 commented Dec 6, 2022

On the Metamask chrome extension, if you go to . . . -> Connected Sites -> (select your site) Disconnect, then it disconnects your wallet.
How is it that we cannot do this programatically?

@mdcoon
Copy link

mdcoon commented Sep 27, 2023

This thread has been open for a while and I know I'm late to the game. But I'm running into an issue with web3-react and this connect/disconnect problem. It seems that if I connect MetaMask to my site and pick account A, then each time I come to the site and choose MetaMask as my preferred wallet, it always picks account A regardless of what's actually selected in MetaMask. There is no indication on the MetaMask UI that the current account is not connected and the site uses the previously-selected address to lookup balances, etc. This is a problem that needs to be addressed as it can lead to unexpected results for users and appears as "bugs" in the dApp when it's actually the wallet selection process.

@dhiraj0911
Copy link

hey @mdcoon
I encountered a similar issue in my dApp while using @web3-react/core. However, I managed to resolve it with a specific approach. Here's what worked for me:

const onclickMetaMaskConnect = async () => {
  setIsConnecting(true);

  try {
    const account = await requestWalletConnection();
    if (account) {
      await activate(injected, undefined, true);
    }
  } catch (error) {
    console.error('Failed to connect MetaMask:', error);
  }

  setIsConnecting(false);
};

const requestWalletConnection = async () => {
  try {
    const permissions = await window.ethereum.request({
      method: "wallet_requestPermissions",
      params: [{ eth_accounts: {} }],
    });

    if (permissions) {
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      return accounts[0];
    } else {
      console.error('Wallet connection permission denied.');
      return null;
    }
  } catch (error) {
    console.error('Error connecting to wallet:', error);
    return null;
  }
};

@idmfx
Copy link

idmfx commented Feb 6, 2024

Connect

@vulebaolong
Copy link

vulebaolong commented Aug 11, 2024

You can try the following method to disconnect wallet => wallet_revokePermissions

only available for the browser extension.

https://docs.metamask.io/wallet/reference/wallet_revokepermissions/
https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-2.md

metamask is successful, but other wallets may fail, you need to check before executing the function "wallet_revokePermissions" to avoid errors

await window.ethereum.request({
  "method": "wallet_revokePermissions",
  "params": [
    {
      "eth_accounts": {}
    }
  ]
});

@TrongNguyen47
Copy link

I saw the MetaMask docs, it provice a method for disconnect to wallet. Let try this:

await window.ethereum.request({
  "method": "wallet_revokePermissions",
  "params": [
    {
      "eth_accounts": {}
    }
  ]
});

@Tukyo
Copy link

Tukyo commented Sep 13, 2024

On the Metamask chrome extension, if you go to . . . -> Connected Sites -> (select your site) Disconnect, then it disconnects your wallet. How is it that we cannot do this programatically?

+1

@efstajas
Copy link

Super happy that MIP-2 made it through 🙌 Thanks folks, that pretty much fixes this issue IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests