From bf9329d48fb5f53bafc2521ae403d64bd8ee750d Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sat, 27 Oct 2018 18:10:00 +0200 Subject: [PATCH] Automatically merged updates to draft EIP(s) 1193 Hi, I'm a bot! This change was automatically merged because: - It only modifies existing Draft or Last Call EIP(s) - The PR was approved or written by at least one author of each modified EIP - The build is passing --- EIPS/eip-1193.md | 225 ++++++++++++++++------------------------------- 1 file changed, 74 insertions(+), 151 deletions(-) diff --git a/EIPS/eip-1193.md b/EIPS/eip-1193.md index 6dd91abe8fda33..160c88e33e3b77 100644 --- a/EIPS/eip-1193.md +++ b/EIPS/eip-1193.md @@ -14,26 +14,12 @@ requires: 1102 This EIP formalizes an Ethereum Provider JavaScript API for consistency across clients and applications. -The provider is designed to be minimal, containing 4 methods: `enable`, `send`, `subscribe`, and `unsubscribe`. It emits 4 types of events: `connect`, `close`, `networkChanged`, and `accountsChanged`. +The provider is designed to be minimal containing 2 methods: `send` and `on`. It emits 5 types of events: `notification`, `connect`, `close`, `networkChanged`, and `accountsChanged`. It is intended to be available on `window.ethereum`. ## API -### Enable - -By default a "read-only" provider is supplied to allow access to the blockchain while preserving user privacy. - -A full provider can be requested to allow account-level methods: - -```js -ethereum.enable(): Promise; -``` - -This shows a dialog to the user asking if they would like to authenticate any account(s) to the dapp. - -Promise resolves with `true` or rejects with `Error`. - ### Send Ethereum API methods can be sent and received: @@ -46,43 +32,29 @@ Promise resolves with `result` or rejects with `Error`. See the [available methods](https://github.com/ethereum/wiki/wiki/JSON-RPC#json-rpc-methods). -### Subscriptions - -#### Subscribe +#### Request Accounts -```js -ethereum.subscribe(subscriptionType: String, subscriptionMethod: String, params?: Array): Promise; -``` +By default, the provider supplied to a new dapp has is a "read-only" provider with no accounts authenticated. -`subscriptionType` is expected to be `eth_subscribe` or `shh_subscribe`. +To request accounts, call `ethereum.send('eth_requestAccounts')`. This will ask the user which account(s) they would like to authenticate to the dapp. -See the [eth subscription methods](https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB#supported-subscriptions) and [shh subscription methods](https://github.com/ethereum/go-ethereum/wiki/Whisper-v6-RPC-API#shh_subscribe). +Promise resolves with an array of the account(s) public keys. -Promise resolves with `subscriptionId: String` or rejects with `Error`. - -Results emit on `subscriptionId` using [EventEmitter](https://nodejs.org/api/events.html). Attach listeners with: +### Events -```js -ethereum.on(subscriptionId, listener: (result: any) => void): this; -``` +Events are emitted using [EventEmitter](https://nodejs.org/api/events.html). -The event emits with `result`, the subscription `result` or an `Error` object. +#### notification -#### Unsubscribe +All subscriptions from the node emit on `notification`. Attach listeners with: ```js -ethereum.unsubscribe(subscriptionType: String, subscriptionId: String): Promise; +ethereum.on('notification', listener: (result: any) => void): this; ``` -`subscriptionType` is expected to be `eth_unsubscribe` or `shh_unsubscribe`. - -Promise resolves with `success: Boolean` or rejects with `Error`. +To create a subscription, call `ethereum.send('eth_subscribe') or`ethereum.send('shh_subscribe'). The subscription `result` object will emit through `notification`. -All [EventEmitter](https://nodejs.org/api/events.html) listeners on `subscriptionId` will also be removed. - -### Events - -Events are emitted using [EventEmitter](https://nodejs.org/api/events.html). +See the [eth subscription methods](https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB#supported-subscriptions) and [shh subscription methods](https://github.com/ethereum/go-ethereum/wiki/Whisper-v6-RPC-API#shh_subscribe). #### connect @@ -158,49 +130,53 @@ ethereum ); }); -// Example 2: Enable full provider +// Example 2: Request accounts ethereum - .enable() - .then(success => { - if (success) { - console.log(`Ethereum provider enabled enabled!`); - - // Example 3: Log available accounts - ethereum - .send('eth_accounts') - .then(accounts => { - console.log(`Accounts:\n${accounts.join('\n')}`); - }) - .catch(error => { - console.error( - `Error fetching accounts: ${error.message}. - Code: ${error.code}. Data: ${error.data}` - ); - }); + .send('eth_requestAccounts') + .then(accounts => { + if (accounts.length > 0) { + console.log(`Accounts enabled:\n${accounts.join('\n')}`); + } else { + console.error(`No accounts enabled.`); } }) .catch(error => { console.error( - `Error enabling provider: ${error.message}. + `Error requesting accounts: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); +// Example 3: Log available accounts +ethereum + .send('eth_accounts') + .then(accounts => { + console.log(`Accounts:\n${accounts.join('\n')}`); + }) + .catch(error => { + console.error( + `Error fetching accounts: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + }); + // Example 4: Log new blocks let subId; ethereum - .subscribe('eth_subscribe', 'newHeads') + .send('eth_subscribe', ['newHeads']) .then(subscriptionId => { subId = subscriptionId; - ethereum.on(subscriptionId, block => { - if (block instanceof Error) { - const error = result; - console.error( - `Error from newHeads subscription: ${error.message}. - Code: ${error.code}. Data: ${error.data}` - ); - } else { - console.log(`New block ${block.number}:`, block); + ethereum.on('notification', result => { + if (result.subscriptionId === subscriptionId) { + if (result.result instanceof Error) { + const error = result.result; + console.error( + `Error from newHeads subscription: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + } else { + console.log(`New block ${block.number}:`, block); + } } }); }) @@ -212,9 +188,9 @@ ethereum }); // to unsubscribe ethereum - .unsubscribe('eth_unsubscribe', subId) + .send('eth_unsubscribe', [subId]) .then(result => { - console.log(`Unsubscribed newHeads subscription ${subscriptionId}`); + console.log(`Unsubscribed newHeads subscription ${subId}`); }) .catch(error => { console.error( @@ -239,16 +215,6 @@ ethereum.on('close', (code, reason) => { ## Specification -### Enable - -The provider supplied to a new dapp **MUST** be a "read-only" provider: authenticating no accounts by default, returning a blank array for `eth_accounts`, and rejecting any methods that require an account with Error code 4100. - -If the dapp has been previously authenticated and remembered by the user, then the provider supplied on load **MAY** automatically be enabled with the previously authenticated accounts. - -If no accounts are authenticated, the `enable` method **MUST** ask the user which account(s) they would like to authenticate to the dapp. If the request has been previously granted and remembered, the `enable` method **MAY** immediately return. - -The `enable` method **MUST** return a Promise that resolves with true or rejects with an `Error`. If the accounts enabled by provider change, the `accountsChanged` event **MUST** also emit. - ### Send The `send` method **MUST** send a properly formatted [JSON-RPC request](https://www.jsonrpc.org/specification#request_object). @@ -261,32 +227,38 @@ If an error occurs during processing, such as an HTTP error or internal parsing If the implementing Ethereum Provider is not talking to an external Ethereum JSON-RPC API provider then it **MUST** resolve with an object that matches the JSON-RPC API object as specified in the [Ethereum JSON-RPC documentation](https://github.com/ethereum/wiki/wiki/JSON-RPC). -If the JSON-RPC request requires an account that is not yet authenticated, the Promise **MUST** reject with an `Error`. +If the JSON-RPC request requires an account that is not yet authenticated, the Promise **MUST** reject with Error code 4100. -### Subscriptions +#### eth_requestAccounts -The `subscribe` method **MUST** send a properly formatted [JSON-RPC request](https://www.jsonrpc.org/specification#request_object) with method `subscriptionType` (`eth_subscribe` or `shh_subscribe`) and params `[subscriptionMethod: String, ...params: Array]`. It **MUST** return a Promise that resolves with `id: String` or rejects with an Error object. +The provider supplied to a new dapp **MUST** be a "read-only" provider: authenticating no accounts by default, returning a blank array for `eth_accounts`, and rejecting any methods that require an account with Error code `4100`. -The `unsubscribe` method **MUST** send a properly formatted [JSON-RPC request](https://www.jsonrpc.org/specification#request_object) with method `subscriptionType` (`eth_unsubscribe` or `shh_unsubscribe`) and params `[subscriptionId: String]`. It **MUST** return a Promise that resolves with `result: Boolean` or rejects with an Error object. +If the dapp has been previously authenticated and remembered by the user, then the provider supplied on load **MAY** automatically be enabled with the previously authenticated accounts. -If the `unsubscribe` method returns successfully with a `True` result, the implementing provider **MUST** remove all listeners on the `subscriptionId` using `ethereum.removeAllListeners(subscriptionId);`. +If no accounts are authenticated, the `eth_requestAccounts` method **MUST** ask the user which account(s) they would like to authenticate to the dapp. If the request has been previously granted and remembered, the `eth_requestAccounts` method **MAY** immediately return. -If an error occurs during processing of the subscription, such as an HTTP error or internal parsing error then the Promise **MUST** return with an Error object. +The `eth_requestAccounts` method **MUST** resolve with an array of the account(s) public keys or reject with an `Error`. If the account(s) enabled by the provider change, the `accountsChanged` event **MUST** also emit. -The implementing Ethereum Provider **MUST** emit every subscription response `result` to the eventName of the `subscriptionId`. +### Events -If an error occurs or the network changes during the listening of the subscription, the Ethereum Provider **MUST** emit an Error object to the eventName of the `subscriptionId`. +#### notification -If the implementing provider does not support subscriptions, then it **MUST** leave the `subscribe` and `unsubscribe` methods undefined. +All subscriptions received from the node **MUST** emit the `message.params` to the eventName `notification` without modification. -### Events +#### connect If the network connects, the Ethereum Provider **MUST** emit an event named `connect`. +#### close + If the network connection closes, the Ethereum Provider **MUST** emit an event named `close` with args `code: Number, reason: String` following the [status codes for `CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes). +#### networkChanged + If the network the provider is connected to changes, the provider **MUST** emit an event named `networkChanged` with args `networkId: String` containing the ID of the new network (using the Ethereum JSON-RPC call `net_version`). +#### accountsChanged + If the accounts connected to the Ethereum Provider change, the Ethereum Provider **MUST** send an event with the name `accountsChanged` with args `accounts: Array` containing the accounts' public key(s). ### Class @@ -303,11 +275,11 @@ If an Error object is returned, it **MUST** contain a human readable string mess Appropriate error codes **SHOULD** follow the table of [`CloseEvent` status codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes), along with the following table: -| Status code | Name | Description | -| ----------- | -------------------------- | ------------------------------------------------------------------------------------------------------- | -| 4001 | User Denied Full Provider | User denied enabling the full Ethereum Provider by choosing not to authorize any accounts for the dapp. | -| 4010 | User Denied Create Account | User denied creating a new account. | -| 4100 | Unauthorized | The requested account has not been authorized by the user. | +| Status code | Name | Description | +| ----------- | ---------------------------- | ---------------------------------------------------------- | +| 4001 | User Denied Request Accounts | User denied authorizing any accounts for the dapp. | +| 4010 | User Denied Create Account | User denied creating a new account. | +| 4100 | Unauthorized | The requested account has not been authorized by the user. | ## Sample Class Implementation @@ -321,7 +293,6 @@ class EthereumProvider extends EventEmitter { this._isConnected = false; this._nextJsonrpcId = 0; this._promises = {}; - this._activeSubscriptions = []; // Fire the connect this._connect(); @@ -332,23 +303,6 @@ class EthereumProvider extends EventEmitter { /* Methods */ - enable() { - return new Promise((resolve, reject) => { - window.mist - .requestAccounts() - .then(accounts => { - if (accounts.length > 0) { - resolve(true); - } else { - const error = new Error('User Denied Full Provider'); - error.code = 4001; - reject(error); - } - }) - .catch(reject); - }); - } - send(method, params = []) { if (!method || typeof method !== 'string') { return new Error('Method is not a valid string.'); @@ -375,29 +329,6 @@ class EthereumProvider extends EventEmitter { return promise; } - subscribe(subscriptionType, subscriptionMethod, params = []) { - return this.send(subscriptionType, [subscriptionMethod, ...params]).then( - subscriptionId => { - this._activeSubscriptions.push(subscriptionId); - return subscriptionId; - } - ); - } - - unsubscribe(subscriptionType, subscriptionId) { - return this.send(subscriptionType, [subscriptionId]).then(success => { - if (success) { - // Remove subscription - this._activeSubscription = this._activeSubscription.filter( - id => id !== subscriptionId - ); - // Remove listeners on subscriptionId - this.removeAllListeners(subscriptionId); - return success; - } - }); - } - /* Internal methods */ _handleJsonrpcMessage(event) { @@ -437,9 +368,8 @@ class EthereumProvider extends EventEmitter { } } else { if (method && method.indexOf('_subscription') > -1) { - // Emit subscription result - const { subscription, result } = message.params; - this.emit(subscription, result); + // Emit subscription notification + this._emitNotification(message.params); } } } @@ -459,6 +389,10 @@ class EthereumProvider extends EventEmitter { /* Events */ + _emitNotification(result) { + this.emit('notification', result); + } + _emitConnect() { this._isConnected = true; this.emit('connect'); @@ -467,17 +401,6 @@ class EthereumProvider extends EventEmitter { _emitClose(code, reason) { this._isConnected = false; this.emit('close', code, reason); - - // Send Error objects to any open subscriptions - this._activeSubscriptions.forEach(id => { - const error = new Error( - `Provider connection to network closed. - Subscription lost, please subscribe again.` - ); - this.emit(id, error); - }); - // Clear subscriptions - this._activeSubscriptions = []; } _emitNetworkChanged(networkId) {