diff --git a/EIPS/eip-1193.md b/EIPS/eip-1193.md new file mode 100644 index 00000000..b2e0e1a2 --- /dev/null +++ b/EIPS/eip-1193.md @@ -0,0 +1,265 @@ +--- +eip: 1193 +title: Ethereum Provider API +author: Ryan Ghods (@ryanio), Marc Garreau (@marcgarreau) +discussions-to: https://ethereum-magicians.org/t/eip-1193-ethereum-provider/640 +status: Draft +type: Standards Track +category: Interface +created: 2018-06-30 +requires: 1102 +--- + +## Summary + +This proposal formalizes an Ethereum Provider API. + +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`. + +## API + +### Send + +```js +ethereum.send(method: String, params?: Array): Promise; +``` + +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 + +```js +ethereum.subscribe(subscriptionType: String, params?: Array): Promise; +``` + +Promise resolves with `subscriptionId: String` or rejects with `Error`. + +See the [types of subscriptions](https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB#supported-subscriptions). + +Results emit on `subscriptionId` using [EventEmitter](https://nodejs.org/api/events.html). Attach listeners with: + +```js +ethereum.on(subscriptionId, listener: (result: any) => void): this; +``` + +The event emits with `result`, the subscription `result` or an `Error` object. + +#### Unsubscribe + +```js +ethereum.unsubscribe(subscriptionId: String): Promise; +``` + +Promise resolves with `success: Boolean` or rejects with `Error`. + +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). + +#### connect + +The provider emits `connect` on connect to a network. + +```js +ethereum.on('connect', listener: () => void): this; +``` + +You can detect which network by sending `net_version`: + +```js +const network = await ethereum.send('net_version'); +> '1' +``` + +#### close + +The provider emits `close` on disconnect from a network. + +```js +ethereum.on('close', listener: (code: Number, reason: String) => void): this; +``` + +The event emits with `code` and `reason`. The code follows the table of [`CloseEvent` status codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes). + +#### networkChanged + +The provider emits `networkChanged` on connect to a new network. + +```js +ethereum.on('networkChanged', listener: (networkId: String) => void): this; +``` + +The event emits with `networkId`, the new network returned from `net_version`. + +#### accountsChanged + +The provider emits `accountsChanged` if the accounts returned from the provider (`eth_accounts`) changes. + +```js +ethereum.on('accountsChanged', listener: (accounts: Array) => void): this; +``` + +The event emits with `accounts`, an array of the accounts' public keys. + +### Constructor + +```js +ethereum.constructor.name; +> 'EthereumProvider' +``` + +## 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); + +function start(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}` + ); + }); + + // 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: 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 => { + 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}` + ); + }); +} +``` + +## Specification + +### 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 an error occurs during processing, such as an HTTP error or internal parsing error then the Promise **MUST** be rejected with an Error object containing a human readable string message describing the error and **SHOULD** populate code and data properties on the error object with additional error details. + +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). In case of an error, ensure that the Promise is rejected with an Error that matches the above shape. + +### 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 containing a human readable string message describing the error and **SHOULD** populate code and data properties on the error object with additional error details. + +The `unsubscribe` method **MUST** send a properly formatted [JSON-RPC request](https://www.jsonrpc.org/specification#request_object) with method `eth_unsubscribe` and params `[subscriptionId: String]` and **MUST** return a Promise that resolves with `result: Boolean` or rejected with an Error object containing a human readable string message describing the error and **SHOULD** populate code and data properties on the error object with additional error details. + +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 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 containing a human readable string message describing the error and **SHOULD** populate code and data properties on the error object with additional error details. + +The implementing Ethereum Provider **MUST** emit every subscription response `result` with the eventName `subscriptionId`. + +If an error occurs during the listening of the subscription, the Ethereum Provider **MUST** emit an Error object to the eventName `subscriptionId` containing a human readable string message describing the error and **SHOULD** populate code and data properties on the error object with additional error details. + +If the implementing provider does not support subscriptions, then it **MUST** leave the `subscribe` and `unsubscribe` methods undefined. + +### Events + +If the network connects, the Ethereum Provider **MUST** emit an event named `connect`. + +If the network connection closes, the Ethereum Provider **MUST** emit an event named `close` with args `code: Number, reason: String` using the [status codes for `CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes) and a short human readable reason. + +If the network the Ethereum Provider is connected to changes, the Ethereum 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`). + +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 + +The name of the constructor of the Ethereum Provider **MUST** be `EthereumProvider`. + +## Topics + +### Multiple chain support + +As per discussion in [ethereum/interfaces#16](https://github.com/ethereum/interfaces/issues/16), to handle support of changing networks we recommend introducing a new RPC method `eth_changeNetwork`. In the future depending on the implementation of sharding, an additional method could be `eth_changeShard`. + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).