diff --git a/EIPS/eip-1193.md b/EIPS/eip-1193.md index afbe3930..bfe09bb4 100644 --- a/EIPS/eip-1193.md +++ b/EIPS/eip-1193.md @@ -14,12 +14,28 @@ requires: 1102 This EIP formalizes an Ethereum Provider JavaScript API for consistency across clients and applications. -The provider is designed to be minimal, containing 3 methods: `send`, `subscribe`, and `unsubscribe`. It emits 4 types of events: `connect`, `close`, `networkChanged`, and `accountsChanged`. +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`. + +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<[String]>; +``` + +Promise resolves with an array of the accounts' public keys, or rejects with `Error`. + ### Send +Ethereum API methods can be sent and received: + ```js ethereum.send(method: String, params?: Array): Promise; ``` @@ -33,7 +49,7 @@ See the [available methods](https://github.com/ethereum/wiki/wiki/JSON-RPC#json- #### Subscribe ```js -ethereum.subscribe(subscriptionType: String, params?: Array): Promise; +ethereum.subscribe(subscriptionType: String, params?: Array): Promise; ``` Promise resolves with `subscriptionId: String` or rejects with `Error`. @@ -51,7 +67,7 @@ The event emits with `result`, the subscription `result` or an `Error` object. #### Unsubscribe ```js -ethereum.unsubscribe(subscriptionId: String): Promise; +ethereum.unsubscribe(subscriptionId: String): Promise; ``` Promise resolves with `success: Boolean` or rejects with `Error`. @@ -117,113 +133,131 @@ ethereum.constructor.name; ## Examples ```js -// Request Ethereum Provider (EIP 1102) -window.addEventListener('message', event => { - if (event.data && event.data.type === 'ETHEREUM_PROVIDER_SUCCESS') { - start(window.ethereum); - } -}); -window.postMessage({ type: 'ETHEREUM_PROVIDER_REQUEST' }, this.origin); +const ethereum = window.ethereum; -function start(ethereum) { - // A) Primary use case - set provider in web3.js - web3.setProvider(ethereum); +// A) Primary use case - set provider in web3.js +web3.setProvider(ethereum); - // B) Secondary use case - use provider object directly - // Example: Log 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}` - ); - }); +// B) Secondary use case - use provider object directly +// Example 1: Log last block +ethereum + .send('eth_getBlockByNumber', ['latest', 'true']) + .then(block => { + console.log(`Block ${block.number}:\n${block}`); + }) + .catch(error => { + console.error( + `Error fetching last block: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + }); - // Example: Log last block - ethereum - .send('eth_getBlockByNumber', ['latest', 'true']) - .then(block => { - console.log(`Block ${block.number}:\n${block}`); - }) - .catch(error => { - console.error( - `Error fetching last block: ${error.message}. - Code: ${error.code}. Data: ${error.data}` - ); - }); +// Example 2: Enable full provider +ethereum + .enable() + .then(accounts => { + console.log(`Enabled accounts:\n${accounts.join('\n')}`); + }) + .catch(error => { + console.error( + `Error enabling provider: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + }); - // Example: Log new blocks - let subId; - ethereum - .subscribe('newHeads') - .then(subscriptionId => { - subId = subscriptionId; - ethereum.on(subscriptionId, block => { - if (result 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}:\n${block}`); - } - }); - }) - .catch(error => { - console.error( - `Error making newHeads subscription: ${error.message}. - Code: ${error.code}. Data: ${error.data}` - ); - }); - // to unsubscribe - ethereum - .unsubscribe(subId) - .then(result => { - console.log(`Unsubscribed newHeads subscription ${subscriptionId}`); - }) - .catch(error => { - console.error( - `Error unsubscribing newHeads subscription: ${error.message}. - Code: ${error.code}. Data: ${error.data}` - ); - }); - - // Example: Log when accounts change - const logAccounts = accounts => { +// Example 3: Log available accounts +ethereum + .send('eth_accounts') + .then(accounts => { console.log(`Accounts:\n${accounts.join('\n')}`); - }; - ethereum.on('accountsChanged', logAccounts); - // to unsubscribe - ethereum.removeListener('accountsChanged', logAccounts); - - // Example: Log if connection ends - ethereum.on('close', (code, reason) => { - console.log( - `Ethereum provider connection closed: ${reason}. Code: ${code}` + }) + .catch(error => { + console.error( + `Error fetching accounts: ${error.message}. + Code: ${error.code}. Data: ${error.data}` ); }); } + +// Example 4: Log new blocks +let subId; +ethereum + .subscribe('newHeads') + .then(subscriptionId => { + subId = subscriptionId; + ethereum.on(subscriptionId, block => { + if (result 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}:\n${block}`); + } + }); + }) + .catch(error => { + console.error( + `Error making newHeads subscription: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + }); +// to unsubscribe +ethereum + .unsubscribe(subId) + .then(result => { + console.log(`Unsubscribed newHeads subscription ${subscriptionId}`); + }) + .catch(error => { + console.error( + `Error unsubscribing newHeads subscription: ${error.message}. + Code: ${error.code}. Data: ${error.data}` + ); + }); + +// Example 5: Log when accounts change +const logAccounts = accounts => { + console.log(`Accounts:\n${accounts.join('\n')}`); +}; +ethereum.on('accountsChanged', logAccounts); +// to unsubscribe +ethereum.removeListener('accountsChanged', logAccounts); + +// Example 6: Log if connection ends +ethereum.on('close', (code, reason) => { + console.log( + `Ethereum provider connection closed: ${reason}. Code: ${code}` + ); +}); ``` ## 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. + +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 with the prior remembered accounts and permissions. + +The `enable` method **MUST** return a Promise, resolving with an array of the accounts' public keys, or rejecting 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). If the Ethereum JSON-RPC API returns a response object with no error, then the Promise **MUST** resolve with the `response.result` object untouched by the implementing Ethereum Provider. -If the Ethereum JSON-RPC API returns response object that contains an error property then the Promise **MUST** be rejected with an Error object containing the `response.error.message` as the Error message, `response.error.code` as a code property on the error and `response.error.data` as a data property on the error. +If the Ethereum JSON-RPC API returns response object that contains an error property then the Promise **MUST** reject with an Error object containing the `response.error.message` as the Error message, `response.error.code` as a code property on the error and `response.error.data` as a data property on the error. -If an error occurs during processing, such as an HTTP error or internal parsing error, then the Promise **MUST** be rejected with an Error object. +If an error occurs during processing, such as an HTTP error or internal parsing error, then the Promise **MUST** reject with an `Error` object. 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`. + ### Subscriptions The `subscribe` method **MUST** send a properly formatted [JSON-RPC request](https://www.jsonrpc.org/specification#request_object) with method `eth_subscribe` and params `[subscriptionType: String, {...params: Array}]` and **MUST** return a Promise that resolves with `subscriptionId: String` or rejected with an Error object. @@ -262,7 +296,13 @@ The implementing Ethereum Provider **MUST** be compatible as a `web3.js` provide If an Error object is returned, it **MUST** contain a human readable string message describing the error and **SHOULD** populate the `code` and `data` properties on the error object with additional error details. -Appropriate error codes **SHOULD** follow the table of [`CloseEvent` status codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes). +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 the enabling of the full Ethereum Provider by choosing not to authorize any accounts for the dapp. | +| | | | +| | | | ## Sample Class Implementation @@ -287,6 +327,15 @@ class EthereumProvider extends EventEmitter { /* Methods */ + enable() { + return new Promise((resolve, reject) => { + window.mist + .requestAccounts() + .then(resolve) + .catch(reject); + }); + } + send(method, params = []) { if (!method || typeof method !== 'string') { return new Error('Method is not a valid string.');