Skip to content

TIP-6963: Multi Injected Provider Discovery #737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
zorro11639 opened this issue Mar 19, 2025 · 3 comments
Open

TIP-6963: Multi Injected Provider Discovery #737

zorro11639 opened this issue Mar 19, 2025 · 3 comments
Labels
enhancement New feature or request

Comments

@zorro11639
Copy link

zorro11639 commented Mar 19, 2025

tip: 6963
title: Multi Injected Provider Discovery
author: Zorro <[email protected]>
discussions to: https://github.com/tronprotocol/tips/issues/737
category: Interface
status: Draft
created: 2025-03-19

Table of Contents

Summary

This protocol specifies an alternative discovery mechanism to window.tron for TIP-1193 providers which supports discovering multiple injected Wallet Providers in a web page using Javascript’s window events.

Abstract

Currently, Tron wallets is recommended to inject their Wallet providers into the same window object window.tron according to TIP-1193. 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 order and the final Provider is usually provided by the last wallet resulting in that users cannot select the specified wallet if the wallet is not loaded last.

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.

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 Tron networks.

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

Specification

Definitions

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

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

interface TIP6963ProviderDetail {
  info: TIP6963ProviderInfo;
  provider: TIP1193Provider;
}

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 TIP6963AnnounceProviderEvent interface must be a CustomEvent object with a type property containing a string value of TIP6963:announceProvider and a detail property with an object value of type TIP6963ProviderDetail. The TIP6963ProviderDetail object should be frozen by calling Object.freeze() on the value of the detail property.

// Announce Event dispatched by a Wallet
interface TIP6963AnnounceProviderEvent extends CustomEvent {
  type: "TIP6963:announceProvider";
  detail: TIP6963ProviderDetail;
}

The TIP6963RequestProviderEvent interface must be an Event object with a type property containing a string value of TIP6963:requestProvider.

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

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

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

let info: TIP6963ProviderInfo;
let provider: TIP1193Provider;

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

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

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

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

The DApp may elect to persist various TIP6963ProviderDetail 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 TIP6963RequestProviderEvent, 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.

Backwards Compatibility

This TIP doesn't require supplanting window.tron, so it doesn't directly break existing applications that cannot update to this method of Wallet discovery. However, it is recommended DApps implement this TIP to ensure discovery of multiple Wallet Providers and should disable window.tron usage except as a fail-over when discovery fails. Similarly, Wallets should keep compatibility of window.tron to ensure backwards compatibility for DApps that have not implemented this TIP. 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.tron namespace.

@chendatony31
Copy link

感觉 这个提案 已经很成熟了, 希望尽快落地,最好 能有个 页面 追踪 钱包和登陆库( tronwallet-adapter 等)的进度

@Benson0224 Benson0224 added the enhancement New feature or request label Mar 28, 2025
@bbsyaya
Copy link

bbsyaya commented Apr 2, 2025

能加一个demo 的js么,包括钱包和dapp的具体实现TIP6963

@zorro11639
Copy link
Author

The following code snippet shows how to implement this protocol:

import { TronWeb } from "tronweb";

interface RequestArguments {
  readonly method: string;
  readonly params?: unknown[] | object;
}

interface TIP1193Provider {
  request: (args: RequestArguments) => Promise<unknown>;
  on(event: string, listener: (...args: any[]) => void): this;
  removeListener(event: string, listener: (...args: any[]) => void): this;
  tronWeb: TronWeb;
  [key: `is${string}`]: boolean;
}

/**
 * Represents the assets needed to display a wallet
 */
interface TIP6963ProviderInfo {
  uuid: string;
  name: string;
  icon: string;
  rdns: string;
}

interface TIP6963ProviderDetail {
  info: TIP6963ProviderInfo;
  provider: TIP1193Provider;
}

// Announce Event dispatched by a Wallet
interface TIP6963AnnounceProviderEvent extends CustomEvent {
  type: "TIP6963:announceProvider";
  detail: TIP6963ProviderDetail;
}

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

// Wallet:
const info: TIP6963ProviderInfo = {
  uuid: 'generated uuid v4',
  name: "TIP6963 Wallet",
  icon: 'your wallet icon',
  rdns: "com.tip1193.wallet",
  
};
const provider: TIP1193Provider = {
  // Your wallet TRON provider
  request: () => {
    return new Promise((resolve, reject) => {
      // Your wallet TRON provider logic
    });
  },
  on() {
    return this;
  },
  removeListener() {
    return this;
  },
  tronWeb: new TronWeb()
};

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



// Dapp:
// The DApp listens to announced providers
window.addEventListener(
  "TIP6963:announceProvider",
  (event: TIP6963AnnounceProviderEvent) => {
    // Here we can store the provider and use it later
  }
);

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

@bbsyaya

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants