solana-mobile / mobile-wallet-adapter

Other
249 stars 102 forks source link

Web Dapps Using Auto Sign Not Working with MWA #490

Open Funkatronics opened 1 year ago

Funkatronics commented 1 year ago

Describe the bug Some dapps attempt to do a sign in flow by automatically triggering a message sign as soon as a wallet is connected. On Chrome for Android (and most if not all other mobile browsers), both app links (eg solana-wallet://) and universal links (eg https://my.app/...) are blocked if they do not come from a direct user action. This causes these sites to not funciton with Mobile Wallet Adapter. The sign message attempt is blocked, and in most cases this causes the site to get stuck in a sort of infinite loop because a cookie is stored for the connected wallet (reload>connected wallet is saved and reconnects>triggers auto sign message again>blocked>reload>repeat).

To Reproduce Steps to reproduce the behavior:

  1. Install Ultimate or Solflare Wallet on Android device
  2. Open mobile browser (issue confirmed on latest Play Store versions for Chrome, Edge, Firefox, Opera)
  3. Navigate to drip.haus
  4. tap on "Connect Wallet" button
  5. if a wallet disambiguation dialog is shown, select the wallet installed in step 1
  6. approve the connection in the wallet, should be returned to the web page
  7. See that the page has auto navigated to {walletUriBase}/v1/associate/...
  8. preloading the page or navigating back to drip.haus will continue to auto navigate to {walletUriBase}/v1/associate/...

Note:

Expected behavior Upon returning to the webpage after approving the connection in the wallet app, subsequent requests sent to the wallet should function as expected (launch the wallet app and continue with MWA request as usual)

Screenshots Using drip.haus and Ultimate:

Using drip.haus and Fakewallet:

Using example web app and Fakewallet:

Smartphone (please complete the following information):

Additional context

I believe this issue is caused by Chrome not being able to verify wallet links that use a https scheme. Per the MWA 1.0 spec, a wallet can supply a walletUriBase field during authorization that the dapp should use to connect to for subsequent requests. Chrome is refusing to open these links as app links, and instead navigates the page to the url. I believe chrome will only open these https links in the wallet app if it can verify the app link

This was confirmed to be false. The issue is almost certainly due to mobile browsers blocking navigation (app links, universal links) that do not originate from a user action (popup blocking).

Funkatronics commented 1 year ago

I think the proper fix here is for the wallets to add the app link verification stuff as described here. I have not yet confirmed this myself, but I believe this will allow chrome to open the wallet uris in the native app instead of navigating the page to that url.

Another workaround might be to modify mobile-wallet-adapter-protocol to use the custom intent: scheme IFF a valid walletUriBase is provided AND the domain cannot be verified. This will require us to implement manual verification, like we do in our digital asset links library. More info here.

Update: further investigation has revealed that even properly verified links still exhibit this behavior so its not related to app link verification.

Funkatronics commented 1 year ago

Question: If my above theories are correct, why do some sites work but others dont? For example, Ultimate wallet does not work with drip.haus (see gif above), but it does work as expected with jup.ag. Is Jupiter using a custom implementation of mobile wallet adapter that works around this issue? Or am I incorrect in my assumptions and something else is causing some apps to navigate to the wallet url instead of launching it as an app link/android intent?

Funkatronics commented 1 year ago

Workaround At this moment, wallets can work around this issue by removing the walletUriBase field that is returned during authorization. This will force wallet adapter on the website to use the solana-wallet:// uri scheme that works generically with all wallets thus circumventing the problem. The wallet could add a check to only remove this field when connecting to a web based dapp, and could continue to use the walletUriBase field for connections with natives apps. Note that this workaround has other negative impacts (wallet disambiguation dialog might be used, webpage will not have a fallback url to take the users to) so should only be used as a temporary solution.

This workaround was found to only work with some sites. Its not a reliable solution, and the solana-wallet:// uri can still be blocked by chromes popup blocker so the above info is not actually correct. Further investigation is needed to understand why some sites (like drip.haus) seem to work with this workaround. Their error logic might give us a clue on how to handle this case.

Funkatronics commented 1 year ago

Proposed Solution:

  1. Add a sign in API to the SolanaMobileWalletAdapter plugin that combines authorize and sign message into a single transact block so that web dapps can perform this "auto" sign in flow without needing to switch back to the web between connecting the wallet and requesting a signature
  2. Add a check inside of the transact method that returns an error if transact was not initiated from a user action. This can be accomplished by adding a new event parameter to the transact method that should represent the user event that initiated the action. The transact method will check this event and verify that this event was user initiated (event.isTrusted). This would be a breaking change so if there is another way to verify that the action originated from a user event I am all ears!

CC @Michaelsulistio @sdlaver @steveluscher

d-reader-josip commented 1 year ago

I would say that there are 2 things to implement here:

  1. from MWA side detect if the event is trusted and if not, throw a proper error so the developer understands that they need to refactor they logic
  2. from WA side we should enrich the WalletButton components to accept callbacks before and after their onClick events like so beforeConnect, afterConnect, beforeDisconnect, afterDisconnect otherwise all the developers looking to support MWA on their web apps will have to implement it on their own if they're ever looking to have support for a single button which both authorizes the app and does any other action like signing a message.

This is the naive approach, there is more to the solution proposed under the point 2. and it's implementation will probably take a lot of thought and time. The biggest issue would be the connect function from the wallet-adapter library which is not friendly enough towards the mobile-wallet-adapter library.

For example, if we want authorize a dApp and sign a message in a single wallet session on mobile web apps, our afterConnect would need to pass in the authorized account and the mobile wallet object from the transacts callback.

Roughly something like this:

await transact(async (mobileWallet) => {
  const freshAccount = await authorizeSession(mobileWallet)
  const account = selectedAccount ?? freshAccount

  if (typeof afterConnect === 'function') {
    await afterConnect(mobileWallet, account)
  }
}

can also be reproduced using our example web app

On a side note, I don't think this issue has anything to do with the example app? That could be an issue of it's own, but shouldn't be this one since the example app does not use useEffect which makes the event not trusted. Right?

cc: @Funkatronics