web3.js icon indicating copy to clipboard operation
web3.js copied to clipboard

Implement EIP-6963 - Multi Injected Provider Discovery

Open spacesailor24 opened this issue 1 year ago • 3 comments

EIP-6963 offers an alternative approach to using window.ethereum for injected providers within the browser. Instead of relying on window.ethereum to be the intended injected provider to be used by libraries like web3.js, this EIP gives us the ability to request available injected providers made available by wallet browser extensions

One way to implement this EIP for web3.js is for us to add a function, eip6963RequestProvider(), that initializes an event lister for eip6963:announceProvider events, and dispatches the event: eip6963:requestProvider:

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

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

interface EIP6963AnnounceProviderEvent extends CustomEvent {
  type: "eip6963:announceProvider";
  detail: EIP6963ProviderDetail;
}

const eip6963ProviderDetails: EIP6963ProviderDetail[] = [];
const initializedEip6963Lister = false;

function eip6963RequestProvider() {
  if (window === undefined)
    throw new Error(
      "window object not available, EIP-6963 is intended to be used within a browser"
    );

  if (!initializedEip6963Lister) {
    // Not sure if this is necessary, but seemed like a good idea
    initializedEip6963Lister = true;

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

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

This function, eip6963RequestProvider, could exist on the dApps instantiated Web3 instance, giving them access to the eip6963ProviderDetails array so that they could display the available wallets/providers to the dApp user as desired

After the web3.js user calls eip6963RequestProvider() and has access to a populated eip6963ProviderDetails, the user could call web3.setProvider(eip6963ProviderDetail) where eip6963ProviderDetail is the dApp user selected provider

References

  • https://eips.ethereum.org/EIPS/eip-6963
  • https://ethereum-magicians.org/t/eip-6963-multi-injected-provider-discovery/14076/5
  • https://github.com/walletconnect/eip6963
  • https://twitter.com/i/spaces/1DXxyvprYjPKM
  • https://eip6963.org/

spacesailor24 avatar Jun 14 '23 04:06 spacesailor24

why doesn't eip6963RequestProvider return Web3PromiEvent so user could do something like

eip6963RequestProvider().on("provider", (providerDetail: EIP6963ProviderDetail) => {
   if(bestProvider) {
      web3 = new Web3(providerDetail.provider)
   }
}) 
`˙`

mpetrunic avatar Jun 19 '23 12:06 mpetrunic

With having more than one provider available, just thinking if we it would be good to have a removal function for EIP6963ProviderDetail

luu-alex avatar Jun 21 '23 01:06 luu-alex

@mpetrunic Yea, that's a good point, we should utilize a promiEvent to give the user the chance to respond to announced providers

So a refactored approach using a promiEvent:

import { Web3PromiEvent } from "web3-core";

interface EIP6963ProviderInfo {
  uuid: string;
  name: string;
  icon: string;
}

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

interface EIP6963AnnounceProviderEvent extends CustomEvent {
  type: "eip6963:announceProvider";
  detail: EIP6963ProviderDetail;
}

export class Web3 {
  static eip6963RequestProvider() {
    const promiEvent = new Web3PromiEvent((resolve, reject) => {
      if (window === undefined)
        reject(
          new Error(
            "window object not available, EIP-6963 is intended to be used within a browser"
          )
        );

      window.addEventListener(
        "eip6963:announceProvider",
        (event: EIP6963AnnounceProviderEvent) =>
          promiEvent.emit(
            "eip6963:announceProvider",
            Object.freeze(event.detail)
          )
      );

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

      resolve();
    });

    return promiEvent;
  }
}

And an example web3.js user's implementation:

import { Web3 } from "web3";

const eip6963ProviderDetails: EIP6963ProviderDetail[] = [];

Web3.eip6963RequestProvider().on(
  "eip6963:announceProvider",
  (providerDetail: EIP6963ProviderDetail) =>
    eip6963ProviderDetails.push(providerDetail)
);

const selectedProvider = await askTheUserWhichWalletToUse(
  eip6963ProviderDetails
);

const web3 = new Web3(selectedProvider);

@luu-alex With the refactored approach, since the web3.js user is now responsible for managing eip6963ProviderDetails, maintaining this list is no longer our responsibility - Web3.js is merely a pass through for provider details

spacesailor24 avatar Jun 21 '23 03:06 spacesailor24