EIP-1193: Ethereum Provider JavaScript API

Why doesn send talks about JSON RPC? The ideas of this provider is to hide the JSON RPC stuff behind the provider, and therefore only accept method and params. In the future there can be other means by which the browser or middle ware talks to a node.

I am not convinced of the requesting of ethereum providers. They should be there by default. As for protecting the users privacy we simply don’t return any accounts (only an empty array), until the user allows accounts to be visible. Without accounts, the dapp can only access common information from the blockchain which is not a problem.

Also from a usability perspective, nobody wants to click OK for every website he visits.

Why doesn send talks about JSON RPC? The ideas of this provider is to hide the JSON RPC stuff behind the provider, and therefore only accept method and params. In the future there can be other means by which the browser or middle ware talks to a node.

The send method does only accept method and params.

I am not convinced of the requesting of ethereum providers. They should be there by default. As for protecting the users privacy we simply don’t return any accounts (only an empty array), until the user allows accounts to be visible. Without accounts, the dapp can only access common information from the blockchain which is not a problem.

We should have more discussion around this, what do other people think? I do think EIP 1102 makes a good argument for the behavior against fingerprinting attacks. I also like the idea of having a standard way you can explicitly request the provider and expect to receive it, instead of relying on it being available in the global namespace.

I don’t think a dapp should have automatic access to the provider due to the potential for abuse (a rogue dapp sending eth_getBlockByNumber 10000 times locking up your machine before you even understand what’s going on.) We could introduce rate limiting in the provider, or maybe how the nodes are designed to handle something like that, but I don’t think a normal webpage should have default access to a provider.

Also from a usability perspective, nobody wants to click OK for every website he visits.

I believe you only visit a small number of dapps, and giving explicit permission the first time you visit a new dapp is okay for most users in such a sensitive environment.

1 Like

The spam attacks from a dapp is a valid concern, but you could do that with javascript today as well. Simply open a 1000 alert windows. Though as we have seen browser protect against that, so could Mist and others, when the see that to many requests are fired by one dapp…
I think usability and easy access by dapps is key. Similar like the chrome object, which is also just “there”. But i am happy to hear more opinions on that.

I believe the argument for not revealing web3 enabled browser is that it gives away information about the user, particularly that they use ETH. This allows advertisers to target you more specifically as well as allows attackers to create more targeted attack vectors (for example, that don’t show up for non-web3 users to reduce the time until they are found).

This is similar to user agents, in that by telling Google I am using Firefox via User Agent, it can advertise to me to use Chrome. Similarly, an attacker can have their attack only execute against Chrome users that are vulnerable to the attack, and lie dormant for Firefox users.

2 Likes

@frozeman: Without sidetracking the discussion around the actual provider API, I feel strongly that exposing either a provider object or web3 globally by default is objectively different than the Chrome browser exposing a chrome global (or any other global on the window object.) A browser-specific utility object like chrome offers no identifying information beyond browser brand, which is already deducible by other means. The mere presence of an Ethereum-specific object uniquely identifies Ethereum users regardless of whether an account is exposed. A more-accurate comparison of this behavior would be if your fiat bank blindly injected an identifying object on every website you visited so any site could know what bank you specifically used (and in some cases, your account number.) MetaMask recently saw a wave of successful, high-profile phishing attempts that specifically targeted users based on web3 availability. Like @MicahZoltu said, the genesis of opt-in exposure is privacy, not spam-prevention.

Further, EIP-1102 lays out a protocol for user-approved provider access, but it doesn’t mandate any specific UX. For example, what if dapp browsers cached which sites a user has approved so it’s a one-time approval? It’s still possible to provide a near-“always there” experience while still maintaining necessary user privacy and giving them control over which sites know they’re Ethereum users.

@ryanio: I think the API is coming together very nicely. Do you plan to define the proposed eth_changeNetwork RPC method, or is this outside the scope of this proposal?

2 Likes

Thanks! Hm, I’m not sure if it’s outside scope, but after thinking about it since every implementing provider will need to handle the request differently, we could include a spec line like this under send:

If the method eth_changeNetwork is sent with params [networkId: String], the implementing provider MUST change the network and on success MUST emit the event networkChanged with the new networkId. If the provider cannot handle changing to the requested network or encounters any other error in the changing process, the provider MUST reject the Promise with an Error object containing a human readable string message describing the error and SHOULD populate the code and data properties on the error object with additional error details.

What do you think?

I think the eth_changeNetwork specification as written above makes sense. However, since this proposal is meant to be specific to the JavaScript provider API, not the underlying RPC API, I worry slightly about this newly-suggested method getting lost. I’d suggest proposing this change directly to the RPC specification, and then retroactively updating the abstracted JavaScript API via this proposal.

1 Like

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