EIP-1193: Ethereum Provider JavaScript API

That sounds like a good idea. After finishing our implementation in Mist, I will see if I can propose eth_changeNetwork as an addition to the RPC specification. Thanks again for your help.

I would also say that account change etc should be future subscriptions. The reason why I introduced the new subscription model back then was to allow many new subscription types like: account changed, state changed, balance changed, etc. to be implemented and added in the future.
This would require no change to the basic subscription ali proposed here, just anew type with or without extra Params.

On the other hand the unsubscribe should also work for “networkChanged” and such. This is currently not specified.
@ryanio you proposal also says it should implement the sendAsync method to be backwards compatible. I would not do that, as the web3 provider used since 2 years has send function being a sync, I would rather let libraries like web3.js deal with handling the different provider types like legacy web3.currentProvider

I like the idea. I am a fan of as thin as a provider as possible, so having those events as subscriptions sounds smart.

How would we go about implementing the new subscription types though, would we need to propose them to the node teams (geth, parity, etc.) to implement? The way it is currently set up, it is much easier for the provider clients (Mist/MetaMask/Status/etc.) to implement and emit when the user changes which accounts are authenticated to the dapp or when the user changes the network.

Right now those events are specified to be emitted using EventEmitter so you would just use window.ethereum.removeAllListeners('networkChanged').

The subscribe/unsubscribe methods as currently defined are meant to explicitly be used for the pub-sub spec.

Ok sounds good, I would much prefer not needing to provide methods to make it compatible. Is there someone on the web3.js team I can get in contact with to help enable the provider as defined in this sample implementation to work as a web3.js provider?

I had to provide sendAsync to get it working as expected, but if we could handle it inside web3.js itself that would be great. I think the lib would need to recognize the EthereumProvider object and use send and then manage the callbacks itself inside the lib’s request manager. At least that’s my understanding of what you meant, let me know if I’m interpreting it incorrectly.

Thanks Fabian!

I’m using Typescript syntax since it’s straightforward to test.

ethereum.send(method: String, params?: Array<any>): Promise<any>;

Why not also return the response ID?


ethereum.subscribe(subscriptionType: String, params?: Array<any>): Promise<String|Error>;

Javascript Promises already reject with Error that is caught using .catch(). There’s no need for an Error return type on top of that.

ethereum.subscribe(subscriptionType: string, params?: any[]): Promise<string>;

ethereum.unsubscribe(subscriptionId: String): Promise<Boolean|Error>;

Same reasoning as above.

ethereum.unsubscribe(subscriptionId: string): Promise<boolean>;

ethereum.on('accountsChanged', listener: (accounts: Array<String>) => void): this;

I think the casing of the account addresses needs to be specified. What about considering this alternative?

ethereum.on('account.added', listener: (account: string) => void)
ethereum.on('account.removed', listener: (account: string) => void)
1 Like

Is there any benefit to have it?

(What would you do with it? How would you want it returned?)

Great point, I’ll update.

I’m not sure, I think it adds more complexity. I’m thinking dapps should be smart enough to react on the accountsChanged array of addresses. Having an event per account doesn’t make much sense if the user adds or removes multiple accounts at once, just creates more handling logic rather than the dapp reevaluating its state on accountsChanged. What do you think?

Thx!

Thanks for your work on this proposal @ryanio. I really like the idea of having a standard provider API.

I’m concerned that forcing the provider constructor’s name to be EthereumProvider is too restrictive, as it makes it harder to extend a provider by composing it.

As an example, this provider wouldn’t be conforming with the standard:

class WrappedEthereumProvider {
    constructor(provider) {
        this._provider = provider;
    }

    async send(method, params) {
        if (this.isSpecialCase(method, params)) {
            return this.specialCase(method, params);
        }

        return this._provider.send(method, params);
    }

    // Rest of the implementation...
}

As a workaround one could always name the provider classes EthereumProvider, but that would create confusion.

I suspect this restriction was added to let developers check if window.ethereum is EIP-1193 without having to expose its class (ie: no window.ethereum instanceof EthereumProvider available). If that’s the case, adding a method isEIP1193Provider() : boolean may be an alternative solution.

Any thoughts?

Also, the EIP suggests that web3.js 1.0.0-beta37 implements this standard, but it doesn’t.

I have two more comments:

  1. eth_requestAccounts returns an array of public keys, not their addresses. This makes it impossible to mimic the behavior in a provider that connects via HTTP to a node. Why not addresses?
  2. It’s not clear if accountsChanged should me emitted right after eth_requestAccounts.

You’re right it should be addresses, I will update the language of the EIP soon. I thought public key === address, but turns out the address is just the last 20 bytes of the public key.

Yes I believe that if accounts ever change, for example after eth_requestAccounts or for any other reason like user choice, accountsChanged should always emit so the dapp knows to update its state.

It seems like it’s still in progress so I will update that, it should be in an upcoming version soon. Thanks.

In this case could you just set the name to EthereumProvider in your own constructor? If its interface behaves similarly then it accomplishes the goal of still being an EthereumProvider, even if it is wrapped, the dapp or end js app doesn’t really need to know - just that it can use the standard interface it’s expecting (send, etc.)

Thanks for your thoughts & comments!

I get it now, thanks for your answer.

Can this be clarified in the EIP? I think the more precise an EIP is, the better.

You can’t do that if you want to extend from the provider. EthereumProvider extends EthereumProvider is a syntax error. You can workaround this by separating them in different modules and importing the original one with an aliased import. But then, isn’t this worth it? Wouldn’t a boolean field be simpler and even easier to check?

And one more question: If I understand correctly eth_subscribe and ssh_subscribe only work with WebSockets or IPC providers. What should an EIP-1193 compatible HTTP provider do if someone does one call like these?

  1. provider.on('notification', ...)
  2. provider.send('eth_subscribe', ...)
  3. provider.send('eth_unsubscribe', ...)
  4. provider.send('shh_subscribe', ...)
  5. provider.send('shh_unsubscribe', ...)

I think this also needs some clarification:

Events are emitted using EventEmitter.

Does it mean that EthereumProvider MUST extend EventEmitter? Or that it MUST implement its interface? Both of them will contradict the initial section that says that EthereumProvider only has two methods.

Another option that comes to my mind is that the intention of that phrase is to spec the order in which listeners are called, copying EventEmitter.prototype.emit semantics.

Thanks!
Patricio

Yes for sure.

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?

I can make a new error code for “Unsupported Method” that could be returned.

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.

Thanks for your thoughts and ideas!
-Ryan

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.