Skip to content

Instantly share code, notes, and snippets.

@corbanbrook
Last active May 12, 2022 08:49
Show Gist options
  • Save corbanbrook/02a36d55644e8c6c1874c89e2e49aaef to your computer and use it in GitHub Desktop.
Save corbanbrook/02a36d55644e8c6c1874c89e2e49aaef to your computer and use it in GitHub Desktop.
Multi Wallet Extension Support

Multi Wallet Extension Support

As more and more web3 wallet projects enter the space, each with their own stregnths and weaknesses, users will have a great deal of choice for not only which wallet they use but which combination of wallets they use. This presents a problem for browser extensions which all expose the window.ethereum provider object.

How do we allow multiple providers to "play nice" with each other?

Problems

1. When multiple wallets extensions are installed which wallet is used?

Depending on the wallet implementation the installed wallets will generally clobber the window.ethereum provider object. Which wallet wins is generally dependent on the order they are executed in and if they prevent others from overriding the object or not.

Much like web browsers which ask "We notice Firefox is not your default browser. Would you like to set it as default? [Yes] [No]",wallets are taking a similar approach, and including internal setting to toggle the wallet as default. The wallet will then either attempt to override window.ethereum and prevent others from modifying that object. This has a few problems because multiple wallet installations have no visibility to each other and each wallet maintains their own isolated state for the users default wallet preference. ie. All wallets could be set to the default wallet at the same time and they will still compete with each other to override the window.ethereum object.

2. How does a Dapp/Consumer detect which wallets are installed and allow user to select their preference when connecting?

Generally consumers look for the the existence of window.ethereum and then detecting the individual provider is mainly done manually by maintaining sets of provider metadata, and checking window.ethereum for is<ProviderName>, eg. isMetaMask. This is not a good solution as it puts the onus on consumers to maintain an ever growing list of providers and their associated metadata.

Many providers also expose additional global namespaces like window.<ProviderName>, consumers can also use these global name spaces for directly connecting a specific provider. This has similar problems as above and also ends up polluting the window global namespace which should probably be avoided whenever possible.

Backwards Compatibility

To maintain backwards compatibility we must keep the existing window.ethereum provider interface. We can however add to it.

Solutions

1. Providers should standardize upon a id property and/or metadata format

At the very least providers should move away from supplying is<ProviderName> and instead standardize on supplying a providerId: string (name TBD, providerName, walletName??) eg. window.ethereum.providerId

This would allow for a single property for consumers to detect which provider is installed.

Expanding upon this we should also standardize on a metadata format which would give consumers additional information to help render. eg window.ethereum.providerInfo

interface ProviderInfo {
  providerId: string // Rather than on the provider root the id could go here?
  displayName?: string
  description?: string
  logoUrl?: string
  websiteUrl?: string
  ...
}

2. Add a common interface and registry to store installed providers and default provider user preference

One proposed solution would be to upgrade window.ethereum to a getter/setter and then add a companion window.ethereumProviders registry.

Below is a simplified summary implementation:

Object.defineProperty(window, "ethereumProviders", {
  value: {
    providers: new Map<string, Provider>(),
    defaultProvider: null,
    
    addProvider(provider: Provider) {
      this.providers.set(provider.providerId, provider)
      
      if (!this.defaultProvider) {
        this.setDefaultProvider(provider)
      }
    },
    
    getProvider(providerId: string): Provider | undefined {
      return this.providers.get(providerId)
    }
    
    getDefaultProvider() {
      return this.defaultProvider
    },
    
    setDefaultProvider(providerId: string) {
      this.defaultProvider = this.getProvider(providerId)
    },
  },
  configurable: false,
  writable: false,
})

Object.defineProperty(window, "ethereum", {
  get() {
    return window.ethereumProviders?.getDefaultProvider()
  },
  set(newProvider) {
    window.ethereumProviders?.addProvider(newProvider)
  },
  configurable: false,
})

Backwards compatibility

This allows for complete backwards compatibility. Wallets setting window.ethereum = provider will be automatically registered and will be set as the defaultProvider if non is already set. window.ethereum will now simply return the defaultProvider.

Wallets adhereing to this standard will need to detect if a previously installed implementation, and if exists just add their provider to the registry.

Optionally, to avoid adding an additional ethereumProviders to the global namespace we could roll it into window.ethereum via a proxy.

Persiting defaultProvider selection

Expanding on this we could persist the user preference defaultProvider per dapp origin or globally(??).

Wallet Default Settings UI

Instead of simply providing a toggle for the default wallet, they can now show the current selected provider or even list all installed providers in a select. This would be vastly better UX.

Dapps/Consumers

Dapps/Consumers can now list all installed providers with helpful metadata, and allow connecting of a specific provider directly via window.ethereumProviders.getProvider(providerId) as well as signify the default wallet.

Users will most likely want to use different wallets depending on the dapp, so it would be helpful for consumers to persist the users selection.

Consumers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment