EIP-2700
FINAL
Final for 2,301 days, since

JavaScript Provider Events

JavaScript Provider Event Emitter

Summary

A standard mechanism for JavaScript Ethereum Providers to notify clients about chain state changes when both are able to interface with each other via a shared JavaScript object.

Why It Matters

EIP-2700 defines a consistent standard for web applications to be notified about changes and events to the chain, such as transaction inclusions or failures. This allows web applications to interact with the wallet and Ethereum blockchain without needing to implement wallet-specific code.

12/19
12 of 19 software wallets support EIP-2700

Software Wallets by EIP-2700 Support

Supported

Base App

Supported
Browser Extension

Source

  • Coinbase Wallet extension on the Chrome Web Store

    The injected provider script of the Chrome Web Store package (requestProvider.js, version 3.149.0, .crx file SHA-256 40e8e5ce04bc3266c7a7bda57a80213b441a8af8afabcd0232cf81036ddd0ce6) exposes an EIP-1193 request method and on/removeListener event subscriptions (EIP-2700). It dispatches eip6963:announceProvider with the name "Coinbase Wallet" and the rdns com.coinbase.wallet, and announces again on each eip6963:requestProvider event (EIP-6963).

    Last retrieved
As of View Base App's full rating →

Ambire

Supported
Browser Extension

MetaMask

Supported
Browser Extension

Source

  • support.metamask.io

    MetaMask browser extension supports standard Ethereum Provider APIs. Mobile app does not inject into web pages.

Uniswap Wallet

Supported
Browser Extension

Rainbow

Supported
Browser Extension

Sources (3)

  • Rainbow supports EIP-1193 and EIP-2700.

    Provider class in the Rainbow provider library
    RainbowProvider.ts L12-117 @dfceaa7
    3import {...8  RequestResponse,9} from './references/messengers';10import { toHex } from './utils/hex';1112export class RainbowProvider extends EventEmitter {13  chainId: ChainIdHex | undefined;14  connected = false;15  isRainbow = true;16  isReady = true;17  isMetaMask = true;18  networkVersion = '1';19  selectedAddress: string | undefined;20  providers: (RainbowProvider | Ethereum)[] | undefined = undefined;2122  requestId = 0;2324  private backgroundMessenger?: IMessenger;25  private providerRequestTransport?: IProviderRequestTransport;2627  [key: string]: unknown;2829  constructor({30    backgroundMessenger,31    providerRequestTransport,32    onConstruct,33  }:34    | {35        backgroundMessenger?: IMessenger;36        providerRequestTransport?: IProviderRequestTransport;37        onConstruct?: ({38          emit,39        }: {40          emit: (event: string, ...args: unknown[]) => void;41        }) => void;42      }43    | undefined = {}) {44    super();45    this.backgroundMessenger = backgroundMessenger;46    this.providerRequestTransport = providerRequestTransport;47    onConstruct?.({ emit: this.emit.bind(this) });4849    // EIP-6963 RainbowInjectedProvider in announceProvider was losing context50    this.bindMethods();51  }5253  bindMethods() {54    for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {55      const value = this[key];56      if (typeof value === 'function' && key !== 'constructor') {57        this[key] = value.bind(this);58      }59    }60  }6162  /**63   * @deprecated – This method is deprecated in favor of the RPC method `eth_requestAccounts`.64   * @link https://eips.ethereum.org/EIPS/eip-1102#providerenable-deprecated65   **/66  async enable() {67    return this.request({ method: 'eth_requestAccounts' });68  }6970  isConnected() {71    return this.connected;72  }7374  async handleChainChanged(chainId: ChainIdHex) {75    this.chainId = chainId;76    this.networkVersion = parseInt(this.chainId, 16).toString();77    this.emit('chainChanged', toHex(String(chainId)));78  }7980  async request({81    method,82    params,83  }: RequestArguments): Promise<RequestResponse | undefined> {84    if (!this.providerRequestTransport) throw new Error('No transport');85    this.backgroundMessenger?.send(86      'rainbow_prefetchDappMetadata',87      window.location.href,88    );89    // eslint-disable-next-line no-plusplus90    const id = this.requestId++;91    const response = await this.providerRequestTransport?.send(92      {93        id,94        method,95        params,96      },97      { id },98    );99100    if (response.id !== id) return;101    if (response.error) throw response.error;102103    switch (method) {104      case 'eth_requestAccounts':105        this.selectedAddress = response.result[0];106        this.connected = true;107        break;108      case 'eth_chainId':109        this.chainId = <ChainIdHex>response.result;110        this.networkVersion = parseInt(this.chainId, 16).toString();111        break;112      default:113        break;114    }115116    return response.result;117  }118119  /** @deprecated – This method is deprecated in favor of `request`. */120  // eslint-disable-next-line @typescript-eslint/no-unused-vars121  async sendAsync(
    Provider events emitted by the extension
    index.ts L40-61 @62ea10c
    3637const rainbowProvider = new RainbowProvider({38  backgroundMessenger,39  providerRequestTransport: providerRequestTransport,40  onConstruct({ emit }) {41    // RainbowInjectedProvider is also used in popup via RainbowConnector42    // here we don't need to listen to anything so we don't need these listeners43    if (isValidUrl(window.location.href)) {44      const host = getDappHost(window.location.href);45      backgroundMessenger?.reply(`accountsChanged:${host}`, async (address) => {46        emit('accountsChanged', [address]);47      });48      backgroundMessenger?.reply(49        `chainChanged:${host}`,50        async (chainId: number) => {51          emit('chainChanged', toHex(String(chainId)));52        },53      );54      backgroundMessenger?.reply(`disconnect:${host}`, async () => {55        emit('accountsChanged', []);56        emit('disconnect', []);57      });58      backgroundMessenger?.reply(`connect:${host}`, async (connectionInfo) => {59        emit('connect', connectionInfo);60      });61    }62  },63});6465if (shouldInjectProvider()) {
  • Rainbow supports EIP-6963.

    EIP-6963 provider announcement in the extension source code
    index.ts L65-80 @62ea10c
    37const rainbowProvider = new RainbowProvider({...40  onConstruct({ emit }) {...61    }62  },63});6465if (shouldInjectProvider()) {66  // eslint-disable-next-line prefer-object-spread67  const providerCopy = Object.create(68    Object.getPrototypeOf(rainbowProvider),69    Object.getOwnPropertyDescriptors(rainbowProvider),70  );71  providerCopy.isMetaMask = false;72  announceProvider({73    info: {74      icon: RAINBOW_ICON_RAW_SVG,75      name: 'Rainbow',76      rdns: 'me.rainbow',77      uuid: uuid4(),78    },79    provider: providerCopy as RainbowProvider as EIP1193Provider,80  });8182  backgroundMessenger.reply(83    'rainbow_ethereumChainEvent',84    async ({

Rabby

Supported
Browser Extension

Source

  • buildinProvider.ts @163a6cc

    Rabby implements the EIP-1193 Provider interface and injects it into web pages. EIP-2700 and EIP-6963 are also supported.

Zerion

Supported
Browser Extension

Sources (5)

  • Zerion supports EIP-1193 and EIP-2700.

    Provider class in the extension source code
    provider.ts L53-185 @da2f643
    49function updateChainId(self: EthereumProvider, chainId: string) {50  self.chainId = chainId;51  self.networkVersion = String(parseInt(chainId, 16));52}53export class EthereumProvider extends JsonRpcProvider {54  accounts: string[];55  chainId: string;56  networkVersion: string;57  isZerion = true;58  isMetaMask?: boolean;59  // Metamask provides this proxy with few experimental functions60  // Some dapps rely on its methods (e.g. app.hop.exchange)61  _metamask?: MetamaskExperimentalNamespace;62  connection: Connection;63  _openPromise: Promise<void> | null = null;6465  nonEip6963Request = false;6667  prefersOtherWalletStrategy?: (params: {68    request: RequestArguments & { id?: number };69    originalError: Error;70  }) => Promise<unknown>;7172  constructor(connection: Connection) {73    super(connection);74    this.connection = connection;75    this.shimLegacy();76    this.chainId = '0x1';77    this.networkVersion = '1';78    this.accounts = [];7980    connection.on(81      'ethereumEvent',82      ({ event, value }: { event: string; value: unknown }) => {83        if (event === 'connect') {84          this.events.emit('connect', { chainId: this.chainId });85          return;86        }8788        if (event === 'chainChanged' && typeof value === 'string') {89          if (value === this.chainId) {90            return;91          }92          updateChainId(this, value);93        }94        if (event === 'accountsChanged' && Array.isArray(value)) {95          if (96            // it's okay to perform search like this because `this.accounts`97            // always has at most one element98            accountsEquals(value, this.accounts)99          ) {100            // Do not emit accountChanged because value hasn't changed101            return;102          } else if (value.length && !isEthereumAddress(value.at(0))) {103            this.accounts = [];104          } else {105            this.accounts = value;106          }107        }108        this.events.emit(event, value);109      }110    );111112    this.open();113  }114115  on(event: string, listener: (params: unknown) => unknown) {116    super.on(event, listener);117    return this;118  }119120  off(event: string, listener: (params: unknown) => unknown) {121    super.off(event, listener);122    return this;123  }124125  markAsMetamask() {126    this.isMetaMask = true;127    this._metamask = new MetamaskExperimentalNamespace();128  }129130  unmarkAsMetamask() {131    this.isMetaMask = false;132  }133134  private async _prepareState() {135    return fetchInitialState(this.connection).then(136      ({ chainId, accounts, walletNameFlags }) => {137        updateChainId(this, chainId);138        this.accounts = accounts;139        if (walletNameFlags.includes(WalletNameFlag.isMetaMask)) {140          this.markAsMetamask();141        }142      }143    );144  }145146  /**147   * Some DApps make unbound calls to request, e.g. https://app.phuture.finance/148   * To handle this, make request bound to instance149   */150  public request = async (151    request: RequestArguments,152    context?: unknown153    // eslint-disable-next-line @typescript-eslint/no-explicit-any154  ): Promise<any> => {155    if (request.method === 'eth_chainId') {156      return Promise.resolve(this.chainId);157    }158    if (request.method === 'eth_accounts' && this.accounts.length) {159      return Promise.resolve(this.accounts);160    }161    let params = request.params;162    if (request.method === 'eth_requestAccounts' && this.nonEip6963Request) {163      params = [164        {165          ...(request.params || [])[0],166          nonEip6963Request: this.nonEip6963Request,167        },168        ...(request.params || []).slice(1),169      ];170    }171    const promise = this._getRequestPromise(172      formatJsonRpcRequestPatched(request.method, params || [], request.id),173      context174    );175    if (request.method === 'eth_requestAccounts' && this.nonEip6963Request) {176      return promise.catch((originalError) => {177        if (this.prefersOtherWalletStrategy) {178          return this.prefersOtherWalletStrategy({ request, originalError });179        } else {180          throw originalError;181        }182      });183    }184    return promise;185  };186187  // eslint-disable-next-line @typescript-eslint/no-explicit-any188  private async _getRequestPromise<Result = any, Params = any>(189    request: JsonRpcRequest<Params>,
    Provider assignment in the extension source code
    in-page.ts L115-145 @da2f643
    93const proxiedProvider = new Proxy(patchedProvider, {...103  set(target, prop, value, receiver) {...111    }112  },113});114115window.ethereum = proxiedProvider;116117dappDetection.onChange(({ dappIsZerionAware }) => {118  if (dappIsZerionAware) {119    // Some libs (such as rainbow) access "isZerion" flag120    // to filter out wallets, and it doesn't mean the dapp is showing121    // the connect button for Zerion specifically. This is why we122    // do not turn off the page observer. But we might change this later.123    // pageObserver.stop();124  }125});126127try {128  Object.defineProperty(window, 'ethereum', {129    configurable: false, // explicitly set to false to disallow redefining the property by other wallets130    get() {131      if (isPaused && competingProviders.hasOtherProviders()) {132        return competingProviders.getFirstOtherProvider();133      }134      dappDetection.onAccessThroughWindow();135      return proxiedProvider;136    },137    set(value: EthereumProvider) {138      dappDetection.handleForeignProvider(value);139      competingProviders.handleForeignProvider({140        foreignProvider: value,141        ourProvider: provider,142      });143    },144  });145} catch {146  // eslint-disable-next-line no-console147  console.warn('Failed to set window.ethereum');148}149
    Event listener methods inherited from the provider dependency
    provider.ts L38-53 @cf54c6d
    15export class JsonRpcProvider extends IJsonRpcProvider {...34  public async disconnect(): Promise<void> {35    await this.close();36  }3738  public on(event: string, listener: any): void {39    this.events.on(event, listener);40  }4142  public once(event: string, listener: any): void {43    this.events.once(event, listener);44  }4546  public off(event: string, listener: any): void {47    this.events.off(event, listener);48  }4950  public removeListener(event: string, listener: any): void {51    this.events.removeListener(event, listener);52  }5354  public async request<Result = any, Params = any>(55    request: RequestArguments<Params>,56    context?: any,57  ): Promise<Result> {
    Provider dependency version pinned in the extension lockfile
    package-lock.json L6502-6505 @da2f643
    1{...6  "packages": {...6497    "node_modules/@walletconnect/environment/node_modules/tslib": {6498      "version": "1.14.1",6499      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",6500      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="6501    },6502    "node_modules/@walletconnect/jsonrpc-provider": {6503      "version": "1.0.13",6504      "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.13.tgz",6505      "integrity": "sha512-K73EpThqHnSR26gOyNEL+acEex3P7VWZe6KE12ZwKzAt2H4e5gldZHbjsu2QR9cLeJ8AXuO7kEMOIcRv1QEc7g==",6506      "dependencies": {6507        "@walletconnect/jsonrpc-utils": "^1.0.8",6508        "@walletconnect/safe-json": "^1.0.2",6509        "tslib": "1.14.1"
  • Zerion supports EIP-6963.

    EIP-6963 provider announcement in the extension source code
    eip6963.ts L23-47 @da2f643
    18type Options = {19  onRequestProvider?: () => void;20  onAccessProvider?: () => void;21};2223export function initializeEIP6963(24  provider: EthereumProvider,25  options?: Options26) {27  const providerDetail = { info, provider };28  Object.defineProperty(providerDetail, 'provider', {29    get() {30      // We add this getter to be able to detect that a dapp supports EIP-696331      // We can't rely solely on 'eip6963:requestProvider', because technically dapp32      // can listen to our initial 'eip6963:announceProvider' event33      // and never dispatch the request event.34      options?.onAccessProvider?.();35      return provider;36    },37  });38  const announceEvent = new CustomEvent('eip6963:announceProvider', {39    detail: Object.freeze(providerDetail),40  });41  window.dispatchEvent(announceEvent);4243  window.addEventListener('eip6963:requestProvider', () => {44    window.dispatchEvent(announceEvent);45    options?.onRequestProvider?.();46  });47}

Phantom

Supported
Browser Extension

NuFi

Supported
Browser Extension

Bitget Wallet

Supported
Browser Extension

OKX Wallet

Supported
Browser Extension

WalletChan

Supported
Browser Extension

Sources (2)

  • WalletChan announces itself to apps with EIP-6963. It also sets the standard provider object that older apps look for.

    WalletChan provider announcement code
    announcement.ts L7-58 @47650cc
    3import { getProviderInstance } from "./providerRegistry";45type WindowWithEthereum = Window & { ethereum?: unknown };67const providerInfo = {8  uuid: crypto.randomUUID(),9  name: "WalletChan",10  icon: WALLET_ICON,11  rdns: "com.walletchan",12};1314export function announceProvider(): void {15  const provider = getProviderInstance();16  if (!provider) return;17  const detail = Object.freeze({18    info: Object.freeze({ ...providerInfo }),19    provider,20  });21  window.dispatchEvent(22    new CustomEvent("eip6963:announceProvider", { detail }),23  );24}2526/** Preserve legacy window.ethereum discovery without weakening EIP-6963. */27export function setWindowEthereum(provider: ImpersonatorProvider): boolean {28  try {29    try {30      delete (window as WindowWithEthereum).ethereum;31    } catch {32      // Existing property may be non-configurable.33    }34    try {35      (window as WindowWithEthereum).ethereum = provider;36      if ((window as WindowWithEthereum).ethereum === provider) return true;37    } catch {38      // Fall through to an explicit descriptor.39    }40    Object.defineProperty(window, "ethereum", {41      value: provider,42      writable: true,43      configurable: true,44      enumerable: true,45    });46    return (window as WindowWithEthereum).ethereum === provider;47  } catch {48    console.warn(49      "WalletChan: Could not set window.ethereum (another wallet may have claimed it).",50      "Dapps supporting EIP-6963 will still be able to discover WalletChan.",51    );52    return false;53  }54}5556export function installProviderAnnouncementListener(): void {57  window.addEventListener("eip6963:requestProvider", announceProvider);58}
  • The provider offers the EIP-1193 request method and sends connect, account change and network change events.

    WalletChan provider code
    provider.ts L22-79 @47650cc
    14function exposedSelectedAddress(address: string): string | null {...18  ) return null;19  return address;20}2122export class ImpersonatorProvider extends EventEmitter {23  isImpersonator = true;24  isMetaMask = true;25  #address: string;26  private chainId: number;27  private dappRpcForwarder = new DappRpcForwarder();2829  constructor(chainId: number, address: string) {30    super();31    this.chainId = chainId;32    this.#address = address;33    // Legacy multi-wallet routers may read provider properties through a Proxy34    // whose receiver is the router rather than this provider. Keep the public35    // projection getter-only while closing over the real private-field owner.36    Object.defineProperty(this, "selectedAddress", {37      configurable: false,38      enumerable: true,39      get: () => exposedSelectedAddress(this.#address),40    });41  }4243  /** MetaMask-compatible legacy view of the origin-authorized account. */44  get selectedAddress(): string | null {45    return exposedSelectedAddress(this.#address);46  }4748  setAddress = (address: string, emitAccountsChanged = true) => {49    this.#address = address;50    if (emitAccountsChanged) this.emit("accountsChanged", [address]);51  };5253  emitConnected = () => {54    this.emit("connect", { chainId: hexValue(this.chainId) });55  };5657  setChainId = (chainId: number) => {58    if (this.chainId === chainId) return;59    this.chainId = chainId;60    this.emit("chainChanged", hexValue(chainId));61  };6263  private rpc = async (method: string, params: any[] = []): Promise<any> => {64    const discovered = await this.dappRpcForwarder.tryRequest(65      this.chainId,66      method,67      params,68    );69    return discovered.forwarded70      ? discovered.result71      : requestRpcThroughContentScript(method, params);72  };7374  request = (request: {75    method: string;76    params?: Array<any>;77  }): Promise<any> => {78    return this.send(request.method, request.params || []);79  };8081  send = async (method: string, params: Array<any> = []): Promise<any> => {82    if (isExecutionPermissionRequestInProgress()) {83      throw makeProviderError(

Not Supported

PillarX

Not Supported
Browser Extension

Unknown

Safe

Unknown
Browser Extension

Frame

Unknown
Browser Extension

Elytro

Unknown
Browser Extension
Browser Extension

Trust Wallet

Unknown
Browser Extension

Anon Wallet

Unknown
Browser Extension