EIP-1193: Ethereum Provider JavaScript API

What was the rationale for the EthereumProvider class name requirement? I worry that this requirement will make debugging things quite difficult when we stack multiple providers together in the future (for example, see truffle-hdwallet-provider).

Is it the intention for this requirement to be enforced by web3 and similar libs in all execution environments, or just in the browser? Either way, I think this requirement is an overstep, as different provider implementations should be capable of differentiating themselves by class name within a single namespace/scope.

Also…

Gotcha, I am trying to keep the interface as simple/minimal as possible. Would there be a way to achieve this without a new method?

Right, the intention is mostly to have the emit and on semantics, but I like the idea of requiring extending EventEmitter because then you get all the goodies along with it that others may find useful.

I find these two statements to be at odds with one another. In the browser who’s definition of EventEmitter should the provider extend? If I bundled my own, how will this requirement be enforced? In the interest of minimizing the footprint required to meet the goals of this EIP, it should be sufficient that only some pieces of the EventEmiter interface are required, but encourage inclusion of the full EventEmitter interface via whatever means are appropriate for the code in question.

I agree with the goal of keeping the interface minimal, but I think that’s better achieved by adding a method or field (e.g. public readonly isEIP1193: boolean) than by imposing a name. The latter makes the name part of the interface, and that has way more implications than a field.

That would be super useful.

I also like the idea of having the full EventEmitter functionality, specially if we could get that by just extending EventEmitter, but I’m afraid that there’s no canonical EventEmitter implementation to extend from. As @benjamincburns pointed out, it’s not clear which one should be used.

To make things slightly worse, node.js’ interface of EventEmitter is not really stable, a few methods have been added in the last couple of versions (e.g. EventEmitter.prototype.off).

IMO the best way to go would be to spec the behavior of on, emit, and removeListener to mimic node’s EventEmitter, but without imposing any particular implementation. This way, implementers can still extend from it, but dapp developers can be sure on what’s available across different dapp browsers.

PS: Sorry for the super late answer @ryanio

Thanks @benjamincburns and @alcuadrado for the feedback and suggestions.

I am submitting a PR now to remove the class name requirement in favor of a public read-only value isEIP1193 as well as defining a minimum viable implementation of EventEmitter (on, emit, removeListener)

Please let me know if you have any additional thoughts.

Best regards,
Ryan Ghods

Hey! Ganache maintainer here, sorry I’m so late to the party! Here are some of my initial thoughts on the spec:


The provider is designed to be minimal containing 2 methods: send and on .

I wonder if should off be required as well? web3.js currently uses it (via removeListener) here: https://github.com/ethereum/web3.js/blob/599851753b4a35b5c11f902c7c592b111e190073/packages/web3-providers/src/providers/EthereumProvider.js#L55-L81


I don’t think this property is necessary. If there is another provider-related EIP in the future that changes the provider interface, we’ll either have to add another isEIPnnnn property or add a version property/getter, while still keeping the isEIP1193 property for backwards compatibility. Also, just because a provider implements this flag does not ensure that it is compliant with the EIP, for instance: Ganache will not be fully compliant due to the addition of eth_requestAccounts (we are fine with the proposal, but likely won’t adhere to it by default). Additionally (and pedantically), “EIP” means “Ethereum Improvement Proposal”, which makes the name of this proposed flag a bit strange to me.

IMO, provider consumers, like web3.js, should either assume that the given provider is an Ethereum Provider or just check that the required send and on methods exist on the provider instance. e.g.:

if (typeof provider.send === "function" && typeof provider.on === "function") {
  return this.providersModuleFactory.createEthereumProvider(provider);
}

It is intended to be available on window.ethereum .

Why recommend that all providers call themselves by the same name? Why recommend that they place themselves in the global namespace at all? I think naming the instance should be up to the user of the library. With that said, referencing window in the spec at all seems out of place, as there are many use cases where a provider will not be used in a browser.


If the implementing Ethereum Provider would like to be compatible with web3.js prior to 1.0.0-beta38 , it MUST provide two methods: sendAsync(payload: Object, callback: (error: any, result: any) => void): void and isConnected(): Boolean.

web3.js (pre 1.0.0-beta.38) does not require that the provider implement isConnected (ganache-core doesn’t currently implement this property).

Hey David, thanks for your comments!

I think removeListener covers what’s needed. I did start with an off for side effects like cancelling subscriptions based on the call, but figured it was too much logic in what was hoping to be a minimal provider wrapper.

Sure, makes sense - thanks. I will remove the property. Checking send and on like you exampled seems like a good way to reliably determine if it’s a provider.

The goal is for JavaScript dapps to all be written in the same way where they can consistently expect an Ethereum provider to live, so they can run cross platform over time without modification.

Ok great ty will update.

Thanks @ryanio!

Note: discourse won’t let me put more than two links… so some of my links are mangled.

Ah, there is just some ambiguity in the EIP; at the top it states that:

The provider is designed to be minimal containing 2 methods: send and on.

And then later:

[…] the provider MAY provide as many methods as it can reasonably provide, but MUST provide at least on, emit, and removeListener.

I’m wondering if requiring that the provider expose an emit method is necessary. Obviously the provider itself needs to be able to emit events/notifications, but the mechanism in which it does so probably doesn’t matter to the dapp. Of course I could be totally wrong here; maybe there are valid use-cases for dapps to emit events via the provider?


Ah OK, that makes sense. I do still have concerns though, though they are mostly related to the discussions in EIP-1102 than they are to EIP-1193:

  • Defining the entry point in this EIP seems like in may be in the domain of an IETF RFC (the IETF documents Internet standards; RFCs are those documents). The reason this may be in the IETF RFC domain is because we may run into compatibility problems with future RFCs that may need to implement an interface on window.ethereum in the future. For example: https [colon] //bugzilla [dot] mozilla [dot] org/show_bug.cgi?id=1075059 and https [colon] //bugzilla [dot] mozilla [dot] org/show_bug.cgi?id=1102219 document issues where individual libraries polluting JS prototypes caused naming issues for browsers. It is possible that in the future browsers (other than Mist) may want to include a window.ethereum object themselves, and this current spec may not be in line with the needs of browsers in the future.
  • polluting the global namespace by default is usually frowned upon.
  • what if a dapp wants to use multiple providers? (EIP-1102 is discussing this already)

I think the recommendation for using window.ethereum is outside the scope of this provider implementation API, and should likely just be documented as the intended namespace for browsers, browser extensions, etc. that inject their own Ethereum Provider into the global context; client libraries like ganache-core should not behave this way. Or perhaps we could just link to EIP-1102 for details on how browsers and extensions should expose their provider API?

Given that @nivida is already hard at work implementing things in web3.js while we’re still revising the draft, I think we should do our best to expedite consensus on this EIP so he doesn’t need to track too much of a moving target.

I’d like to propose that we schedule an open-invite zoom call next week or the week after to discuss this EIP. We can create a thread in the providers ring for the necessary people to weigh in on timezones & availability, and we can announce it at the all core devs call on Friday.

I think having all of the concerned parties discussing this synchronously will get us to consensus a lot quicker. I’d also suggest we do the usual thing the foundation does and stream/post the call to YouTube for added transparency/recordkeeping.

@ryanio - if you agree with this, perhaps you could start the scheduling thread? If you don’t have the bandwidth for it at the moment, David or I can set it up. Also assuming we move forward with the idea, we should link to the scheduling thread over on this web3 issue as well, as I think there are a number of people watching that case who weren’t well represented in initial discussions of this EIP.

Thanks @benjamincburns, yes agreed. If you want to take the lead on organizing a call, @marcgarreau and I would be happy to support getting people together to attend.

Thanks I will reword/clarify that.

Yeah, I’ve heard dapps are already using the provider to emit their own events and I don’t think it should be used that way. The only events the provider should emit are what is being specified in the EIP. If most people are extending EventEmitter, I’m not sure if there’s a way to make emit private like that though - please let me know if there is.

Understood, thanks. Yes it is intended mostly as a recommendation since we’re seeing the ecosystem fragment based on which primary provider a dapp is designing around (MetaMask, Status, Mist, etc). JS dapps should easily be compatible across all providers so they can run in any environment, but that’s not the case today. Consistently expecting a provider to show up on window.ethereum seemed to be the best way to achieve that.

Thanks @ryanio! Gauging interest for the call here: EIP 1193: Provider Ring Call/Meeting

Agreed that dapps shouldn’t be using the provider’s event emitter for their own use. There’s no reasonable way of forcing node’s EventEmitter’s emit method to be private; i’m only suggesting that the EIP doesn’t require that the method exist as part of the public Provider API – the mechanism in which the provider emits events should be up to the provider. A rudimentary implementation of a provider could be something like:

  // EthereumProvider.js example that just emits an event for
  // all calls to `send`

  const _emit = Symbol('_emit');
  const _subscriptions = new WeakMap();

  class EthereumProvider {
    constructor() {
      _subscriptions.set(this, {});
    }

    // _emit is a private method because it can't be accessed
    // without having access to the _emit Symbol.
    [_emit](name, data) {
      if (_subscriptions.get(this).hasOwnProperty(name)) {
        _subscriptions.get(this)[name].forEach((callback) => {
          callback(data);
        });
      }
    }

    on(name, callback) {
      let subscription;
      if (_subscriptions.get(this).hasOwnProperty(name)) {
        subscription = _subscriptions.get(this)[name];
        subscription.push(callback);
      } else {
        _subscriptions.get(this)[name] = [callback];
      }
    }

    removeListener(name, callback) {
      if (_subscriptions.get(this).hasOwnProperty(name)) {
        if (!callback) {
          delete _subscriptions.get(this)[name];
        } else {
          const subscription = _subscriptions.get(this)[name];
          const index = subscription.indexOf(callback);
          if (index >= -1) {
            subscription.splice(index, 1);
          }
        }
      }
    }

    send(method, data) {
      return new Promise((resolve) => {
        this[_emit](method, data);
        resolve();
      });
    }
  }

  module.exports = EthereumProvider;

And example usage (should only log "data" to the console once):

  const ethereum = new EthereumProvider();

  function echo(data) {
    console.log(data);
  }

  ethereum.on("echo", echo);

  await ethereum.send("echo", "data");

  ethereum.removeListener("echo", echo);

  await ethereum.send("echo", "data");

Maybe there are valid use cases for dapps to use a Provider’s emit, though? I’d like to discuss this on our upcoming call. It’s likely not going to cause a problem for anyone if we do require emit as part of the provider’s public API, even though Dapps may abuse it.

Hi,

I missed the last few messages, so sorry for this long reply.

@davidmurdoch, you made some really good points in your last few messages! I agree with you that there are some things that should be out of scope of this proposal.

Modifying the DOM being part of an RFC seems right. Also, I think that how to feature detect if a provider implements this proposal should probably be discussed there too.

Keeping this proposal focused just on the API of the Ethereum Provider would make it move forward much faster.

I proposed the isEIP1193 field because it would be useful for dapp and libraries authors, but I agree with removing it.

I’m not sure how detection would work without it. Checking for send and on function would incorrectly tell you that a MetaMask provider as injected today implements this EIP.

Anyway, I don’t think this should be discussed here nor mentioned in the EIP, but in another document.

I’m interested in this EIP because Buidler exposes a provider to their users. Unlocking accounts doesn’t make sense there either.

I think we could make the initial locked state of the accounts optional. With some explanation that it is a security sensitive decision.

Probably some clarification about accountsChanged should be added in case of the provider starting with its accounts unlocked. Should it be emitted anyway? When? I think emitting events at arbitrary times (e.g. when initializing it) will lead to race conditions. When should dapp developers subscribe to it?

If we agree that exposing the provider to dapps should be treated in another document, I think the initial state of accounts should also be there. It seems to be a dapp browsers and wallet extensions specific problem.

I agree. Having that method should be up to the implementation. Some implementations would end up extending from EventEmitter and exposing it, and that’s ok. But there’s no need to forcing all of them to expose it.

I think having an off method would be nice to have a more consistent API. My concern is that many implementations would extend node’s EventEmitter, and it only has off in node 10+.

Maybe we should go with addListener & removeListener which have been available since EventEmitter was added. The thing is that on is far more used than addListener, so on & removeListener doesn’t seem like a bad option to me.

The last thing I’d like to comment is that the proposal assumes that the provider has a sateful connection, and requires from it to emit connect and close events. There’s no such a thing as “connection” in Buidler, and AFAIK there isn’t in ganache-core.

Also, as I mention before, I think firing events in arbitrary times (e.g. when the internal provider connection is open) will lead to race conditions.

I think an optional section for stateful providers can be added, or another document can extend this one with that.

I said as much on discord, but I’ll repeat myself here for completeness.

On the original issue which was the basis for this EIP it seemed like there was a strong consensus that the goal behind the discussion was to define the API for ethereum browser environments. That goal seems to have been lost in translation when the EIP was written. In fact, it seems like the EIP doesn’t state any reason for its own existence.

@ryanio, can I please request that the EIP be updated to clearly define its goals?

1 Like

Just found out about this EIP and I love it already!

This is very complementary with WalletConnect v1.0 Protocol and shares a lot of the same API endpoints which I’m very happy with

I have one proposal for this EIP, could we use chainId instead of networkId. Complying to the EIP-155, chainId won’t conflict while there is no restrictions for networkId.

Additionally it would make the Ethereum Provider Javascript API instantly compatible with any EVM blockchain

I would replace the networkChanged event with the following chainChanged


chainChanged

The provider emits chainChanged on connect to a new network.

ethereum.on('chainChanged', listener: (chainId: String) => void): this;

The event emits with chainId , the new chain returned from eth_chainId .


[EDIT] I wrote a Twitter thread on my main arguments for using chainId over networkId

I think a prefixed name like client_networkChanged and client_accountsChanged would be the most accurate name for it and if required we can also add a client_chainChanged. But we should not forget that this EIP-1193 should just define the provider and environment specific events should be specified in a separate EIP.

Ethereum JSON-RPC spec supports also Batch requests is there a reason why those are not included in the provider api?

I started working on a browser plugin that functions as a web3 provider and came to realize that the patterns followed by the community currently, and encouraged in this EIP, are not supported by browsers and in fact browsers take steps to try to prevent this sort of pattern.

When a browser extension interacts with a page, they see a sandboxed version of that page that does not share JavaScript objects between each other. This means that a plugin cannot (without resorting to some pretty terrible hackery) add an ethereum object to the window and expect page scripts to be able to see/use that.

While I recognize that I’m late to the game, I would encourage the community to adopt practices for communicating with browser extensions that are more widely supported by the browser community, rather than using techniques that browsers are actively trying to discourage/block.

For example, we could use messages for communicating with the ethereum provider, which is something that has wide browser support and is the “correct” way to have a page communicate information to a plugin, or a parent window (in the case where someone builds a wrapping page for providing Ethereum access, rather than a plugin).

While I understand that there are backward compatibility issues with changing the mechanism by which dapps communicate with ethereum providers, I think that it would not be terribly difficult for someone to author a simple adapter that would just convert from window.ethereum.* to window.postMessage(*).

4 Likes

I think we got down into this rabbit hole in the first place because of the web3 library. We wanted a way to give the convenience of the web3 library to dapps / browsers in the same way that it’s handy inside a node. The way a dapp developer instantiates web3 is with new Web3(window.ethereum)… so extensions like MM have done everything possible to facilitate this.

I agree with your points strongly btw, but I think unless we get a full fledged dapp-to-extension convenience library that wraps the post message interface with all the rpc calls, then we’re shit out of luck :smiley:

If someone were to author an adapter library that converted window.ethereum calls into window.postMessage to conform to the recommendations for communication between page/extension or page/iframe host (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#Communicating_with_the_web_page), is there interest in updating this EIP to follow that instead?

Long term, dapps really should be using window.postMessage directly (likely through the use of a library like ethers or web3) rather than doing window.ethereum.sendAsync or window.web3.currentProvider.sendAsync, but for backward compatibility reasons I do agree that an adapter library will be necessary (likely injected using MetaMask’s horrific hackery solution).

2 Likes

I think this could be easily fixed by managing the ethereum provider as part of the Dapp.

If Dapp developers installed the ethereum provider library instead of expecting an injected provider we could then build the hooks using window.postMessage and communicate to browsers extensions just as easily.

I think this wouldn’t necessarily change the interface for EIP-1193 but I agree that it would be a better practice than injecting the provider into the webpage.