EIP-1193: Ethereum Provider JavaScript API

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.

I’ve made a PR to the ethereum/EIPs repo to include the proposed change of replacing the networkChanged event with chainChanged event.

3 Likes

There seems to be some confusing language regarding ethereum.enable and steps forward.

EIP-1193 [1] depends on EIP-1102 [2]. But EIP-1102 says that ethereum.enable() is deprecated in favour of eth_requestAccounts . However, eth_requestAccounts was recently removed [3] from EIP-1193. So what is the right way forward?

[1] https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md
[2] https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md#providerenable-deprecated
[3] https://github.com/ethereum/EIPs/pull/2092/files

Sorry for the confusion. eth_requestAccounts is what you should be using moving forward.

In my opinion it was removed from the EIP prematurely, since it should have been defined in another EIP first so it could be properly linked before being completely removed from 1193.

Hello everyone,

I’ve opened a PR to simplify the error codes defined in this EIP: Simplify EIP 1193 error codes by rekmarks · Pull Request #2240 · ethereum/EIPs · GitHub

For context, I discussed this matter with @ryanio earlier this week, although he has not spoken for or against these changes.

I greatly appreciate your feedback.

Edit: Ryan merged it. Thanks everybody :bowing_man:

1 Like