Back

Sweating the details

Part 1: Intentional product design around multi-wallet user experiences

Nicholas Charriere

|

Apr 25, 2023

At Privy, we build simple tools to help you onboard and engage your users in web3. In this series, we break down some of the work it takes to create simple experiences with state of the art tooling in web3.

We spend a lot of time thinking about the user experience surrounding user activation, and in particular connecting wallets to apps.

Wallets enable true self-custody, but often at the cost of confusing UX.

To that end, Privy strives to make wallet flows more intuitive for end users, and easier to manage for developers.

In this post, we’ll see that there is a lot of state complexity in this domain: users can enter the connection flow from many different states, and the wallet can be disconnected or switched from outside of the app context!

Let’s dive into a concrete example and see how Privy solves for it carefully. At a high-level, our heuristics are:

  • Wallets integrations should work, independently of what state the user comes in with.

  • Wallets integrations should account for state changes within the wallet client.

  • Wallet integrations should provide UIs to help the user understand what to do to get to a desired state, when necessary.

Today we’re going to zoom into a particularly subtle issue around MetaMask connections in a multi-wallet world, that behaves differently based on the user having zero, one or more accounts connected in MetaMask, and also which is the primary selected one.

The privy SDK allows multiple wallets to be linked to one user

Down the rabbithole

First, let’s define some terms and some context. The Privy Auth SDK allows users to link multiple wallets to their user account. This is a very useful feature that customers love, but it comes with a slew of subtle edge cases we need to handle carefully.

Why is it so subtle? Like a lot of hard programming problems, it’s due to non-trivial state. Wallets can be:

  • Connected. The official documentation defines it (EIP-1193) as follows: “The Provider is said to be “connected” when it can service RPC requests to at least one chain.”

  • Linked. This means authenticated in the privy SDK. With wallets, it means the user has actually signed a message (using EIP-4361: SIWE).

  • Active. This is a concept of the Privy SDK. This is the wallet which the developer wants to use to take an on-chain action (like signing, sending ETH, or performing a transaction).

Likewise, we should distinguish between

  • Wallet addresses.The actual public address for an account on-chain. For instance 0x1dA4FDf7029bDf8ff11f28141a659f6563940642 or pushix.eth.

  • Wallet clients. The software provider helping users manage their keys, typically what people mean by “wallet”, for instance MetaMask, Phantom or Coinbase wallet.

In addition to the above SDK state (connected-linked-active), wallet clients can have their own wallet state. Most of them, like shown in MetaMask in the below image, have a “primary” selected wallet at any given time.

MetaMask’s “selected” wallet

A given wallet address (e.g. pushix.eth) can be provided by different wallet clients — by importing the seed phrase — for example by both MetaMask or Coinbase wallet, each of which has subtle differences! The relationship between clients and addresses is therefore many-to-many. If this is still confusing, go check out this short explanation on wallets and external accounts.

We can start to see the state complexity appear! Wallets can be linked but not connected, connected but not linked, both, active or not, across different clients, etc…

Sweating the details

Authentication in a multi-modal world (email, phone, wallets) can be tricky business, but is the only way to build rich and engaging experiences, so it’s worth the pain. Let’s shed some light on this by taking an example user story: let’s imagine we’re building an NFT marketplace app which allows users to link wallets together to display the user’s aggregate NFT gallery, but also allows users to mint or purchase NFTs to one of their connected wallets.

Session 1

  • User signs up with "address1" with the wallet client MetaMask

  • User links a phone, to take advantage of Privy’s mobile SMS login flows and log in on their phone later (where they do not have MetaMask installed)

  • The app encourages the user to link more wallets, for a richer gallery profile -> user links "address2" with MetaMask

Session 2

  • User is on mobile, uses their phone number to login, shows off their gallery to a friend on the go. This experience doesn’t involve their wallet.

Session 3

  • User is on Desktop again, has used SMS to login again, and wants to perform an on-chain action (purchase a sweet new NFT). This requires a wallet to be not only linked to their account (which is already the case), but also connected to the current app.

  • The developer was intentional here, and wants to connect a specific wallet: address1, not any wallet (maybe because of some on-chain properties of that wallet: it is on a whitelist which allows it to perform a gated transaction).

  • They call Privy’s "setActiveWallet(address1)" function, which ensures that the user will connect that specific address and return an ethers or web3 provider to handle the transaction logic.

OK, now that we have this story in place, let’s look at some nuanced state combinations which can happen, and how Privy handles them with care.

Case 1: Target address is connected to the site and selected in the wallet client.

The happy path! The user’s metamask is already connected to the site, and is also the selected wallet in metamask, this means that "eth_requestAccounts" returns this address, and sending transactions through  will forward to that one.

This is the easy case! And this is what happens in a non-multi-wallet world most of the time. This just works, so we’ll keep looking at harder ones.

Case 2: Target address is connected to the site, but not selected in the wallet client.

The user’s metamask is connected to the site, but with the wrong address. So the developer is calling "setActive(address1)" but the user has "address2" as the “selected” wallet in metamask. In this case, Privy’s SDK will nudge the user to switch the wallet in MetaMask, and MetaMask’s UI is quite helpful and intuitive.

MetaMask makes it easy to connect another address if at least one is already connected

In this case, since MetaMask’s UI is acceptable, so Privy simply gives the user a helpful hint and they can do the switch on their own. This is surprisingly manageable for people that have multiple MetaMask addresses, probably because they are in the habit of switching them like this already.

Case 3: Target address is not connected to the site.

The user’s metamask has no addresses connected to the current site. This could happen for a multitude of reasons, first of which is that users can choose to disconnect sites independently, at any time, from MetaMask directly (we love user control, this is a good thing!).

Unlike in case 2, MetaMask does not do a good job of connecting an address to the site from the extension, if no address is connected. The user has to know about an obscure flow, shown below:

This mysterious flow to connect from the MetaMask client directly when no wallet is connected is *hard*

In practice, little to no one knows about this flow, meaning users are unable to connect to the site.

Privy adds a little big detail for this case, and only this one, which gives the user the option to open the account picker and select the intended "address1" and connect it. See below:

This will only show up in this specific case

Privy goes further to make sure that the user indeed connects address1 and not another one, and gives the user feedback to help them through if they didn’t. This is to compensate for the lack of more powerful APIs in MetaMask: they don’t give the option to connect "address1" directly, but only open the account picker, so the user can choose which account to connect. Sometimes, it is great to give the user the choice, but other times — like in the example above where we seek to specifically connect address because of its on-chain properties — developers want more control. See the flow below

What if the user gets it wrong and selects another wallet than the intended one from the developer?

Privy to the rescue!

This example shows but one, small and rare edge case, that a good connector (like Privy) can optimize and make more usable. There are many more such examples of sweating the details in Privy. Privy’s mission is to make onboarding to web3 easier for all users, meeting them where they are in their web3 journey.

As we saw in this post, there is a lot more nuanced complexity that needs to be handled than meets the eye when it comes to web3 connection & authentication flows.

To be clear, wallets are a core piece of infrastructure and as such, should be treated with care and built around standards (and great work is being done here by the likes of CASA, Spruce and others). Nonetheless, our users’ lives should be simple, regardless of how they connect to the applications we build.

It’s easy to only think about the happy path and ignore rarer ones that degrade user experience.

Privy spends a great deal of time optimizing for these and making them as usable as possible for end users, no matter where they are in their web3 journey. Progressive onboarding means meeting users where they are, and often that means smoothing out the UX of these edge case flows.

You can play around with this yourself at https://demo.privy.io

If you are building an awesome web3 product and want to focus on your business domain, not the long and complex tail of web3 Auth edge cases like this one, Privy is here to help! Sign up to get access today at privy.io.

Share this post


RELATED POSTS