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?
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.
To maintain backwards compatibility we must keep the existing window.ethereum
provider interface. We can however add to it.
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,
})
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.
Expanding on this we could persist the user preference defaultProvider per dapp origin or globally(??).
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 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.
- dapps
- web3-react https://github.com/NoahZinsmeister/web3-react
- Web3Modal https://github.com/Web3Modal/web3modal
- RainbowKit https://github.com/rainbow-me/rainbowkit
- wagmi https://github.com/tmm/wagmi