Skip to content

Commit

Permalink
Automatically merged updates to draft EIP(s) 1193
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ryanio authored and eip-automerger committed Oct 27, 2018
1 parent 729bb4a commit bf9329d
Showing 1 changed file with 74 additions and 151 deletions.
225 changes: 74 additions & 151 deletions EIPS/eip-1193.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean>;
```

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:
Expand All @@ -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<any>): Promise<String>;
```
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<Boolean>;
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

Expand Down Expand Up @@ -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);
}
}
});
})
Expand All @@ -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(
Expand All @@ -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).
Expand All @@ -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<any>]`. 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<String>` containing the accounts' public key(s).

### Class
Expand All @@ -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

Expand All @@ -321,7 +293,6 @@ class EthereumProvider extends EventEmitter {
this._isConnected = false;
this._nextJsonrpcId = 0;
this._promises = {};
this._activeSubscriptions = [];

// Fire the connect
this._connect();
Expand All @@ -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.');
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -459,6 +389,10 @@ class EthereumProvider extends EventEmitter {

/* Events */

_emitNotification(result) {
this.emit('notification', result);
}

_emitConnect() {
this._isConnected = true;
this.emit('connect');
Expand All @@ -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) {
Expand Down

0 comments on commit bf9329d

Please sign in to comment.