Alert Source Discuss
Standards Track: Interface

EIP-6963: Multi Injected Provider Discovery

Using window events to announce injected Wallet Providers

Authors Pedro Gomes (@pedrouid), Kosala Hemachandra (@kvhnuke), Richard Moore (@ricmoo), Gregory Markou (@GregTheGreek), Kyle Den Hartog (@kdenhartog), Glitch (@glitch-txs), Jake Moxey (@jxom), Pierre Bertet (@bpierre), Darryl Yeo (@darrylyeo), Yaroslav Sergievsky (@everdimension)
Created 2023-05-01
Requires EIP-1193

Abstract

An alternative discovery mechanism to window.ethereum for EIP-1193 providers which supports discovering multiple injected Wallet Providers in a web page using Javascript’s window events.

Motivation

Currently, Wallet Provider that offer browser extensions must inject their Ethereum providers (EIP-1193) into the same window object window.ethereum; however, this creates conflicts for users that may install more than one browser extension.

Browser extensions are loaded in the web page in an unpredictable and unstable order, resulting in a race condition where the user does not have control over which Wallet Provider is selected to expose the Ethereum interface under the window.ethereum object. Instead, the last wallet to load usually wins.

This results not only in a degraded user experience but also increases the barrier to entry for new browser extensions as users are forced to only install one browser extension at a time.

Some browser extensions attempt to counteract this problem by delaying their injection to overwrite the same window.ethereum object which creates an unfair competition for Wallet Providers and lack of interoperability.

In this proposal, we present a solution that focuses on optimizing the interoperability of multiple Wallet Providers. This solution aims to foster fairer competition by reducing the barriers to entry for new Wallet Providers, along with enhancing the user experience on Ethereum networks.

This is achieved by introducing a set of window events to provide a two-way communication protocol between Ethereum libraries and injected scripts provided by browser extensions thus enabling users to select their wallet of choice.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC-2119.

Definitions

Wallet Provider: A user agent that manages keys and facilitates transactions with Ethereum.

Decentralized Application (DApp): A web page that relies upon one or many Web3 platform APIs which are exposed to the web page via the Wallet.

Provider Discovery Library: A library or piece of software that assists a DApp to interact with the Wallet.

Provider Info

Each Wallet Provider will be announced with the following interface EIP6963ProviderInfo. The values in the EIP6963ProviderInfo MUST be included within the EIP6963ProviderInfo object. The EIP6963ProviderInfo MAY also include extra extensible properties within the object. If a DApp does not recognize the additional properties, it SHOULD ignore them.

  • uuid - a globally unique identifier the Wallet Provider that MUST be (UUIDv4 compliant) to uniquely distinguish different EIP-1193 provider sessions that have matching properties defined below during the lifetime of the page. The cryptographic uniqueness provided by UUIDv4 guarantees that two independent EIP6963ProviderInfo objects can be separately identified.
  • name - a human-readable local alias of the Wallet Provider to be displayed to the user on the DApp. (e.g. Example Wallet Extension or Awesome Example Wallet)
  • icon - a URI pointing to an image. The image SHOULD be a square with 96x96px minimum resolution. See the Images/Icons below for further requirements of this property.
  • rdns - The Wallet MUST supply the rdns property which is intended to be a domain name from the Domain Name System in reverse syntax ordering such as com.example.subdomain. It’s up to the Wallet to determine the domain name they wish to use, but it’s generally expected the identifier will remain the same throughout the development of the Wallet. It’s also worth noting that similar to a user agent string in browsers, there are times where the supplied value could be unknown, invalid, incorrect, or attempt to imitate a different Wallet. Therefore, the DApp SHOULD be able to handle these failure cases with minimal degradation to the functionality of the DApp.
/**
 * Represents the assets needed to display a wallet
 */
interface EIP6963ProviderInfo {
  uuid: string;
  name: string;
  icon: string;
  rdns: string;
}

Images/Icons

A URI-encoded image was chosen to enable flexibility for multiple protocols for fetching and rendering icons, for example:

# svg (data uri)
data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32"><circle fill="red" cx="16" cy="16" r="12"/></svg>
# png (data uri)
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==

The icon string MUST be a data URI as defined in RFC-2397. The image SHOULD be a square with 96x96px minimum resolution. The image format is RECOMMENDED to be either lossless or vector based such as PNG, WebP or SVG to make the image easy to render on the DApp. Since SVG images can execute Javascript, applications and libraries MUST render SVG images using the <img> tag to ensure no untrusted Javascript execution can occur.

RDNS

The rdns (Reverse-DNS) property serves to provide an identifier which DApps can rely on to be stable between sessions. The Reverse Domain Name Notation is chosen to prevent namespace collisions. The Reverse-DNS convention implies that the value should start with a reversed DNS domain name controlled by the Provider. The domain name should be followed by a subdomain or a product name. Example: com.example.MyBrowserWallet.

  • The rdns value MUST BE a valid RFC-1034 Domain Name;
  • The DNS part of the rdns value SHOULD BE an active domain controlled by the Provider;
  • DApps MAY reject the Providers which do not follow the Reverse-DNS convention correctly;
  • DApps SHOULD NOT use the rnds value for feature detection as these are self-attested and prone to impersonation or bad incentives without an additional verification mechanism; feature-discovery and verification are both out of scope of this interface specification.

Provider Detail

The EIP6963ProviderDetail is used as a composition interface to announce a Wallet Provider and related metadata about the Wallet Provider. The EIP6963ProviderDetail MUST contain an info property of type EIP6963ProviderInfo and a provider property of type EIP1193Provider defined by EIP-1193.

interface EIP6963ProviderDetail {
  info: EIP6963ProviderInfo;
  provider: EIP1193Provider;
}

Window Events

In order to prevent provider collisions, the DApp and the Wallet are expected to emit an event and instantiate an eventListener to discover the various Wallets. This forms an Event concurrency loop.

Since the DApp code and Wallet code aren’t guaranteed to run in a particular order, the events are designed to handle such race conditions.

To emit events, both DApps and Wallets MUST use the window.dispatchEvent function to emit events and MUST use the window.addEventListener function to observe events. There are two Event interfaces used for the DApp and Wallet to discover each other.

Announce and Request Events

The EIP6963AnnounceProviderEvent interface MUST be a CustomEvent object with a type property containing a string value of eip6963:announceProvider and a detail property with an object value of type EIP6963ProviderDetail. The EIP6963ProviderDetail object SHOULD be frozen by calling Object.freeze() on the value of the detail property.

// Announce Event dispatched by a Wallet
interface EIP6963AnnounceProviderEvent extends CustomEvent {
  type: "eip6963:announceProvider";
  detail: EIP6963ProviderDetail;
}

The EIP6963RequestProviderEvent interface MUST be an Event object with a type property containing a string value of eip6963:requestProvider.

// Request Event dispatched by a DApp
interface EIP6963RequestProviderEvent extends Event {
  type: "eip6963:requestProvider";
}

The Wallet MUST announce the EIP6963AnnounceProviderEvent to the DApp via a window.dispatchEvent() function call. The Wallet MUST add an EventListener to catch an EIP6963RequestProviderEvent dispatched from the DApp. This EventListener MUST use a handler that will re-dispatch an EIP6963AnnounceProviderEvent. This re-announcement by the Wallet is useful for when a Wallet’s initial Event announcement may have been delayed or fired before the DApp had initialized its EventListener. This allows the various Wallet Providers to react to the DApp without the need to pollute the window.ethereum namespace which can produce non-deterministic wallet behavior such as different wallets connecting each time.

The Wallet dispatches the "eip6963:announceProvider" event with immutable contents and listens to the "eip6963:requestProvider" event:

let info: EIP6963ProviderInfo;
let provider: EIP1193Provider;

const announceEvent: EIP6963AnnounceProviderEvent = new CustomEvent(
  "eip6963:announceProvider",
  { detail: Object.freeze({ info, provider }) }
);

// The Wallet dispatches an announce event which is heard by
// the DApp code that had run earlier
window.dispatchEvent(announceEvent);

// The Wallet listens to the request events which may be
// dispatched later and re-dispatches the `EIP6963AnnounceProviderEvent`
window.addEventListener("eip6963:requestProvider", () => {
  window.dispatchEvent(announceEvent);
});

The DApp MUST listen for the EIP6963AnnounceProviderEvent dispatched by the Wallet via a window.addEventListener() method and MUST NOT remove the Event Listener for the lifetime of the page so that the DApp can continue to handle Events beyond the initial page load interaction. The DApp MUST dispatch the EIP6963RequestProviderEvent via a window.dispatchEvent() function call after the EIP6963AnnounceProviderEvent handler has been initialized.

// The DApp listens to announced providers
window.addEventListener(
  "eip6963:announceProvider",
  (event: EIP6963AnnounceProviderEvent) => {}
);

// The DApp dispatches a request event which will be heard by 
// Wallets' code that had run earlier
window.dispatchEvent(new Event("eip6963:requestProvider"));

The DApp MAY elect to persist various EIP6963ProviderDetail objects contained in the announcement events sent by multiple wallets. Thus, if the user wishes to utilize a different Wallet over time, the user can express this within the DApp’s interface and the DApp can immediately elect to send transactions to that new Wallet. Otherwise, the DApp MAY re-initiate the wallet discovery flow via dispatching a new EIP6963RequestProviderEvent, potentially discovering a different set of wallets.

The described orchestration of events guarantees that the DApp is able to discover the Wallet, regardless of which code executes first, the Wallet code or the DApp code.

Rationale

The previous proposal introduced mechanisms that relied on a single, mutable window object that could be overwritten by multiple parties. We opted for an event-based approach to avoid the race conditions, the namespace collisions, and the potential for “pollution” attacks on a shared mutable object; the event-based orchestration creates a bidirectional communication channel between wallet and dapp that can be re-orchestrated over time.

To follow the Javascript event name conventions, the names are written in present tense and are prefixed with the number of this document (EIP6963).

Interfaces

Standardizing an interface for provider information (EIP6963ProviderInfo) allows a DApp to determine all information necessary to populate a user-friendly wallet selection modal. This is particularly useful for DApps that rely on libraries such as Web3Modal, RainbowKit, Web3-Onboard, or ConnectKit to programmatically generate such selection modals.

Regarding the announced provider interface (EIP6963ProviderDetail), it was important to leave the EIP-1193 provider interface untouched for backwards compatibility; this allows conformant DApps to interface with wallets conforming to either, and for Wallets conformant to this spec to still inject EIP-1193 providers for legacy DApps. Note that a legacy dapp or a DApp conformant with this spec connecting to a legacy wallet cannot guarantee the correct wallet will be selected if multiple are present.

Backwards Compatibility

This EIP doesn’t require supplanting window.ethereum, so it doesn’t directly break existing applications that cannot update to this method of Wallet discovery. However, it is RECOMMENDED DApps implement this EIP to ensure discovery of multiple Wallet Providers and SHOULD disable window.ethereum usage except as a fail-over when discovery fails. Similarly, Wallets SHOULD keep compatibility of window.ethereum to ensure backwards compatibility for DApps that have not implemented this EIP. In order to prevent the previous issues of namespace collisions, it’s also RECOMMENDED that wallets inject their provider object under a wallet specific namespace then proxy the object into the window.ethereum namespace.

Reference Implementation

Wallet Provider

Here is a reference implementation for an injected script by a Wallet Provider to support this new interface in parallel with the existing pattern.

function onPageLoad() {
  let provider: EIP1193Provider;

  window.ethereum = provider;

  function announceProvider() {
    const info: EIP6963ProviderInfo = {
      uuid: "350670db-19fa-4704-a166-e52e178b59d2",
      name: "Example Wallet",
      icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
      rdns: "com.example.wallet"
};
    window.dispatchEvent(
      new CustomEvent("eip6963:announceProvider", {
        detail: Object.freeze({ info, provider }),
      })
    );
  }

  window.addEventListener(
    "eip6963:requestProvider",
    (event: EIP6963RequestProviderEvent) => {
      announceProvider();
    }
  );

  announceProvider();
}

DApp implementation

Here is a reference implementation for a DApp to display and track multiple Wallet Providers that are injected by browser extensions.

const providers: EIP6963ProviderDetail[];

function onPageLoad() {

  window.addEventListener(
    "eip6963:announceProvider",
    (event: EIP6963AnnounceProviderEvent) => {
      providers.push(event.detail);
    }
  );

  window.dispatchEvent(new Event("eip6963:requestProvider"));
}

Security Considerations

EIP-1193 Security considerations

The security considerations of EIP-1193 apply to this EIP. Implementers are expected to consider and follow the guidance of the providers they’re utilizing as well.

Prototype Pollution of Wallet Provider objects

Browser extensions, and therefore Wallet extensions, are able to modify the contents of the page and the Provider object by design. The provider objects of various Wallets are considered a highly trusted interface to communicate transaction data. In order to prevent the page or various other extensions from modifying the interaction between the DApp and the Wallet in an unexpected way, the best practice is to “freeze” the provider discovery object by utilizing object.freeze() on the EIP1193Provider object before the wallet dispatches it in the eip6963:announceProvider Event. However, there are difficulties that can occur around web compatibility where pages need to monkey patch the object. In scenarios like this there’s a tradeoff that needs to be made between security and web compatibility that Wallet implementers are expected to consider.

Wallet Imitation and Manipulation

Similarly so, DApps are expected to actively detect for misbehavior of properties or functions being modified in order to tamper with or modify other wallets. One way this can be easily achieved is to look for when the uuid property within two EIP6963ProviderInfo objects match. DApps and DApp discovery libraries are expected to consider other potential methods that the EIP6963ProviderInfo objects are being tampered with and consider additional mitigation techniques to prevent this as well in order to protect the user.

Prevent SVG Javascript Execution

The use of SVG images introduces a cross-site scripting risk as they can include JavaScript code. This Javascript executes within the context of the page and can therefore modify the page or the contents of the page. So when considering the experience of rendering the icons, DApps need to take into consideration how they’ll approach handling these concerns in order to prevent an image being used as an obfuscation technique to hide malicious modifications to the page or to other wallets.

Prevent Wallet Fingerprinting

One advantage to the concurrency Event loop utilized by this design is that it operates in a manner where either the DApp or the Wallet can initiate the flow to announce a provider. For this reason, Wallet implementers can now consider whether or not they wish to announce themselves to all pages or attempt alternative means in order to reduce the ability for a user to be fingerprinted by the injection of the window.ethereum object. Some examples, of alternative flows to consider would be to wait to inject the provider object until the DApp has announced the eip6963:requestProvider. At that point, the wallet can initiate a UI consent flow to ask the user if they would like to share their wallet address. This allows for the Wallet to enable the option of a “private connect” feature. However, if this approach is taken, Wallets must also consider how they intend to support backwards compatibility with a DApp that does not support this EIP.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Pedro Gomes (@pedrouid), Kosala Hemachandra (@kvhnuke), Richard Moore (@ricmoo), Gregory Markou (@GregTheGreek), Kyle Den Hartog (@kdenhartog), Glitch (@glitch-txs), Jake Moxey (@jxom), Pierre Bertet (@bpierre), Darryl Yeo (@darrylyeo), Yaroslav Sergievsky (@everdimension), "EIP-6963: Multi Injected Provider Discovery," Ethereum Improvement Proposals, no. 6963, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6963.