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
This commit is contained in:
Ryan Ghods 2018-07-23 13:32:56 -07:00 committed by EIP Automerge Bot
parent 153bce3091
commit a8e18356ea

View File

@ -2,7 +2,7 @@
eip: 1193
title: Ethereum Provider JavaScript API
author: Ryan Ghods (@ryanio), Marc Garreau (@marcgarreau)
discussions-to: https://ethereum-magicians.org/t/eip-1193-ethereum-provider/640
discussions-to: https://ethereum-magicians.org/t/eip-1193-ethereum-provider-javascript-api/640
status: Draft
type: Standards Track
category: Interface
@ -33,7 +33,7 @@ See the [available methods](https://github.com/ethereum/wiki/wiki/JSON-RPC#json-
#### Subscribe
```js
ethereum.subscribe(subscriptionType: String, params?: Array<any>): Promise<any>;
ethereum.subscribe(subscriptionType: String, params?: Array<any>): Promise<String|Error>;
```
Promise resolves with `subscriptionId: String` or rejects with `Error`.
@ -220,23 +220,23 @@ If the Ethereum JSON-RPC API returns a response object with no error, then the P
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 the `code` and `data` properties on the error object with additional error details.
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 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.
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).
### 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<any>}]` 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 the `code` and `data` properties on the error object with additional error details.
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<any>}]` and **MUST** return a Promise that resolves with `subscriptionId: String` or rejected with an Error object.
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 the `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.
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 the `code` and `data` properties on the error object with additional error details.
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 implementing Ethereum Provider **MUST** emit every subscription response `result` with the eventName `subscriptionId`.
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 `subscriptionId` 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.
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 `subscriptionId`.
If the implementing provider does not support subscriptions, then it **MUST** leave the `subscribe` and `unsubscribe` methods undefined.
@ -244,9 +244,9 @@ If the implementing provider does not support subscriptions, then it **MUST** le
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 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).
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 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`).
If the accounts connected to the Ethereum Provider change, the Ethereum Provider **MUST** send an event with the name `accountsChanged` with args `accounts: Array<String>` containing the accounts' public key(s).
@ -254,6 +254,197 @@ If the accounts connected to the Ethereum Provider change, the Ethereum Provider
The name of the constructor of the Ethereum Provider **MUST** be `EthereumProvider`.
### web3.js Provider
The implementing Ethereum Provider **MUST** be compatible as a `web3.js` provider. This is accomplished by providing two methods in the `EthereumProvider`: `sendAsync(payload: Object, callback: (error: any, result: any) => void): void` and `isConnected(): Boolean`.
### Error object and codes
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).
## Example Class Implementation
```js
class EthereumProvider extends EventEmitter {
constructor() {
// Call super for `this` to be defined
super();
// Init storage
this._isConnected = false;
this._nextJsonrpcId = 0;
this._promises = {};
this._activeSubscriptions = [];
// Fire the connect
this._connect();
// Listen for jsonrpc responses
window.addEventListener('message', this._handleJsonrpcMessage.bind(this));
}
/* Methods */
send(method, params = []) {
if (!method || typeof method !== 'string') {
return new Error('Method is not a valid string.');
}
if (!(params instanceof Array)) {
return new Error('Params is not a valid array.');
}
const payload = {
id: this._nextJsonrpcId++,
jsonrpc: '2.0',
method,
params
};
const promise = new Promise((resolve, reject) => {
this._promises[payload.id] = { resolve, reject };
});
// Send jsonrpc request to node
window.postMessage({ type: 'write', message: payload }, origin);
return promise;
}
subscribe(subscriptionType, params) {
return this.send('eth_subscribe', [subscriptionType, ...params]).then(
subscriptionId => {
this._activeSubscriptions.push(subscriptionId);
}
);
}
unsubscribe(subscriptionId) {
return this.send('eth_unsubscribe', [subscriptionId]).then(success => {
if (success) {
// Remove subscription
this._activeSubscription = this._activeSubscription.filter(
id => id !== subscriptionId
);
// Remove listeners on subscriptionId
this.removeAllListeners(subscriptionId);
}
});
}
/* Internal methods */
_handleJsonrpcMessage(event) {
// Return if no data to parse
if (!event || !event.data) {
return;
}
let data;
try {
data = JSON.parse(event.data);
} catch (error) {
// Return if we can't parse a valid object
return;
}
// Return if not a jsonrpc response
if (!data || !data.message || !data.message.jsonrpc) {
return;
}
const message = data.message;
const { id, method, error, result } = message;
if (typeof id !== 'undefined') {
const promise = this._promises[id];
if (promise) {
// Handle pending promise
if (data.type === 'error') {
promise.reject(message);
} else if (message.error) {
promise.reject(error);
} else {
promise.resolve(result);
}
delete this.promises[id];
}
} else {
if (method && method.indexOf('_subscription') > -1) {
// Emit subscription result
const { subscription, result } = message.params;
this.emit(subscription, result);
}
}
}
/* Connection handling */
_connect() {
// Send to node
window.postMessage({ type: 'create' }, origin);
// Reconnect on close
this.once('close', this._connect.bind(this));
}
/* Events */
_emitConnect() {
this._isConnected = true;
this.emit('connect');
}
_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) {
this.emit('networkChanged', networkId);
}
_emitAccountsChanged(accounts) {
this.emit('accountsChanged', accounts);
}
/* web3.js provider compatibility */
sendAsync(payload, callback) {
return this.send(payload.method, payload.params)
.then(result => {
const response = payload;
response.result = result;
callback(null, response);
})
.catch(error => {
callback(error, null);
// eslint-disable-next-line no-console
console.error(
`Error from EthereumProvider sendAsync ${payload}: ${error}`
);
});
}
isConnected() {
return this._isConnected;
}
}
```
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).