Skip to content

Dip 6963


dip: 6963 title: Multi Injected Provider Discovery description: Using window events to announce injected Wallet Providers author: 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) Digitalia editing author: Cosimo Constantinos cosimo@juro.net, et al. discussions-to: https://digitalia-magicians.org/t/dip-6963-multi-injected-provider-interface-aka-mipi/14076 status: Final type: Standards Track category: Interface created: 2023-05-01 Created for Digitalia: 2025-01-07 requires: 1193


Abstract

An alternative discovery mechanism to window.digitalia for DIP-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 digitalia providers (DIP-1193) into the same window object window.digitalia; 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 Digitalia interface under the window.digitalia 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.digitalia 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 Digitalia networks.

This is achieved by introducing a set of window events to provide a two-way communication protocol between digitalia 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 digitalia.

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 DIP6963ProviderInfo. The values in the DIP6963ProviderInfo MUST be included within the DIP6963ProviderInfo object. The DIP6963ProviderInfo 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 DIP-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 DIP6963ProviderInfo 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 DIP6963ProviderInfo {
  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 DIP6963ProviderDetail is used as a composition interface to announce a Wallet Provider and related metadata about the Wallet Provider. The DIP6963ProviderDetail MUST contain an info property of type DIP6963ProviderInfo and a provider property of type DIP1193Provider defined by DIP-1193.

interface DIP6963ProviderDetail {
  info: DIP6963ProviderInfo;
  provider: DIP1193Provider;
}

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 DIP6963AnnounceProviderEvent interface MUST be a CustomEvent object with a type property containing a string value of dip6963:announceProvider and a detail property with an object value of type DIP6963ProviderDetail. The DIP6963ProviderDetail object SHOULD be frozen by calling Object.freeze() on the value of the detail property.

// Announce Event dispatched by a Wallet
interface DIP6963AnnounceProviderEvent extends CustomEvent {
  type: "dip6963:announceProvider";
  detail: DIP6963ProviderDetail;
}

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

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

The Wallet MUST announce the DIP6963AnnounceProviderEvent to the DApp via a window.dispatchEvent() function call. The Wallet MUST add an EventListener to catch an DIP6963RequestProviderEvent dispatched from the DApp. This EventListener MUST use a handler that will re-dispatch an DIP6963AnnounceProviderEvent. 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.digitalia namespace which can produce non-deterministic wallet behavior such as different wallets connecting each time.

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

let info: DIP6963ProviderInfo;
let provider: DIP1193Provider;

const announceEvent: DIP6963AnnounceProviderEvent = new CustomEvent(
  "dip6963: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 `DIP6963AnnounceProviderEvent`
window.addEventListener("dip6963:requestProvider", () => {
  window.dispatchEvent(announceEvent);
});

The DApp MUST listen for the DIP6963AnnounceProviderEvent 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 DIP6963RequestProviderEvent via a window.dispatchEvent() function call after the DIP6963AnnounceProviderEvent handler has been initialized.

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

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

The DApp MAY elect to persist various DIP6963ProviderDetail 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 DIP6963RequestProviderEvent, 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 (DIP6963).

Interfaces

Standardizing an interface for provider information (DIP6963ProviderInfo) 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 (DIP6963ProviderDetail), it was important to leave the DIP-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 DIP-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 DIP doesn't require supplanting window.digitalia, so it doesn't directly break existing applications that cannot update to this method of Wallet discovery. However, it is RECOMMENDED DApps implement this DIP to ensure discovery of multiple Wallet Providers and SHOULD disable window.digitalia usage except as a fail-over when discovery fails. Similarly, Wallets SHOULD keep compatibility of window.digitalia to ensure backwards compatibility for DApps that have not implemented this DIP. 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.digitalia 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: DIP1193Provider;

  window.digitalia = provider;

  function announceProvider() {
    const info: DIP6963ProviderInfo = {
      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("dip6963:announceProvider", {
        detail: Object.freeze({ info, provider }),
      })
    );
  }

  window.addEventListener(
    "dip6963:requestProvider",
    (event: DIP6963RequestProviderEvent) => {
      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: DIP6963ProviderDetail[];

function onPageLoad() {

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

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

Security Considerations

DIP-1193 Security considerations

The security considerations of DIP-1193 apply to this DIP. 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 DIP1193Provider object before the wallet dispatches it in the dip6963: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 DIP6963ProviderInfo objects match. DApps and DApp discovery libraries are expected to consider other potential methods that the DIP6963ProviderInfo 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.digitalia object. Some examples, of alternative flows to consider would be to wait to inject the provider object until the DApp has announced the dip6963: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 DIP.

© Crown © Crown Copyright 2026. Published by the Royal Government of the Dominion of Atlantis.

Licensed under the Juro Restricted License Version 2. See https://juro.net/jrl for details.