EIP-1193: Ethereum Provider JavaScript API

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

Made a package implementing all JSON RPC 2.0 and EIP 1193 errors: https://www.npmjs.com/package/eth-json-rpc-errors

Includes support for serializing errors in a standard-compliant way while e.g. handling RPC requests.

As we agreed in the Devcon EIP1193 meeting, I created an issue to discuss that EIP. It has a summary of the meeting and a list of next steps.

Why does this re-use the send name instead of using a new name? ethereum.send(options, callback) is an existing method that some providers are providing in the wild today and dapps rely on. This change makes it so ethereum.send now has a different call signature, but it does not provide a mechanism for detecting whether the provider is using the new signature or the old one.

If this used a different function name, then dapp code could do

if ('newMethod' in window.ethereum) await window.ethereum.newMethod(...)
else await promisify(window.ethereum.newMethod)(...)

However, since the name of the method is the same, only the signature changes, and JS doesn’t provide an easy way of checking call signatures, I now don’t have a reasonable mechanism for being compatible with both old and new providers.

Another potential option would be to provide a version field or something on window.ethereum that allows me to check to see what exactly is implemented. Then I could do if (window.ethereum.version >= 2) await window.ethereum.send(...).

1 Like

Just in case someone here isn’t following this EIP’s discussion, it has been moved to Last Call, and will be finalized in 6 weeks.