2018-07-17 10:50:16 +00:00
---
eip: 1193
2018-07-22 17:13:16 +00:00
title: Ethereum Provider JavaScript API
2019-03-03 13:14:33 +00:00
author: Fabian Vogelsteller (@frozeman), Ryan Ghods (@ryanio), Marc Garreau (@marcgarreau), Victor Maia (@MaiaVictor)
2018-07-23 20:32:56 +00:00
discussions-to: https://ethereum-magicians.org/t/eip-1193-ethereum-provider-javascript-api/640
2018-07-17 10:50:16 +00:00
status: Draft
type: Standards Track
category: Interface
created: 2018-06-30
2019-06-27 05:45:08 +00:00
requires: 155, 695, 1102, 1474
2018-07-17 10:50:16 +00:00
---
## Summary
2019-02-24 22:16:51 +00:00
This EIP formalizes an Ethereum Provider JavaScript API for consistency across clients and applications. The provider is designed to be minimal and is intended to be available on `window.ethereum` for cross environment compatibility.
2018-07-17 10:50:16 +00:00
## API
### Send
2018-09-10 15:41:17 +00:00
Ethereum API methods can be sent and received:
2018-07-17 10:50:16 +00:00
```js
ethereum.send(method: String, params?: Array< any > ): Promise< any > ;
```
Promise resolves with `result` or rejects with `Error` .
See the [available methods ](https://github.com/ethereum/wiki/wiki/JSON-RPC#json-rpc-methods ).
2018-10-27 16:10:00 +00:00
### Events
2018-07-17 10:50:16 +00:00
2018-10-27 16:10:00 +00:00
Events are emitted using [EventEmitter ](https://nodejs.org/api/events.html ).
2018-07-17 10:50:16 +00:00
2018-10-27 16:10:00 +00:00
#### notification
2018-07-17 10:50:16 +00:00
2019-06-03 18:10:13 +00:00
All subscriptions from the node emit on notification. Attach listeners with:
2018-07-17 10:50:16 +00:00
```js
2019-06-03 18:10:13 +00:00
ethereum.on('notification', listener: (result: any) => void): this;
2018-07-17 10:50:16 +00:00
```
2019-06-03 18:10:13 +00:00
To create a subscription, call `ethereum.send('eth_subscribe', [])` or `ethereum.send('shh_subscribe', [])` .
2018-07-17 10:50:16 +00:00
2018-10-27 16:10:00 +00:00
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 ).
2018-07-17 10:50:16 +00:00
#### 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 ).
2019-06-27 05:45:08 +00:00
#### chainChanged
The provider emits `chainChanged` on connect to a new chain.
```js
ethereum.on('chainChanged', listener: (chainId: String) => void): this;
```
The event emits with `chainId` , the new chain returned from `eth_chainId` .
2018-07-17 10:50:16 +00:00
#### 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< String > ) => void): this;
```
2019-01-25 21:36:21 +00:00
The event emits with `accounts` , an array of the accounts' addresses.
2018-07-17 10:50:16 +00:00
## Examples
```js
2018-09-10 15:41:17 +00:00
const ethereum = window.ethereum;
2019-03-03 13:14:33 +00:00
// A) Set provider in web3.js
var web3 = new Web3(ethereum);
2019-06-03 18:10:13 +00:00
// web3.eth.getBlock('latest', true).then(...)
2018-09-10 15:41:17 +00:00
2019-03-03 13:14:33 +00:00
// B) Use provider object directly
2018-09-10 15:41:17 +00:00
// Example 1: Log last block
ethereum
.send('eth_getBlockByNumber', ['latest', 'true'])
.then(block => {
2018-10-05 10:10:36 +00:00
console.log(`Block ${block.number}:`, block);
2018-09-10 15:41:17 +00:00
})
.catch(error => {
console.error(
`Error fetching last block: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
);
});
2018-07-17 10:50:16 +00:00
2019-06-03 18:10:13 +00:00
// Example 2: Log available accounts
2018-10-27 16:10:00 +00:00
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}`
);
});
2019-03-03 13:14:33 +00:00
2019-06-03 18:10:13 +00:00
// Example 3: Log new blocks
2018-09-10 15:41:17 +00:00
let subId;
ethereum
2018-10-27 16:10:00 +00:00
.send('eth_subscribe', ['newHeads'])
2018-09-10 15:41:17 +00:00
.then(subscriptionId => {
subId = subscriptionId;
2019-06-03 18:10:13 +00:00
ethereum.on('notification', result => {
2018-10-27 17:42:11 +00:00
if (result.subscription === subscriptionId) {
2018-10-27 16:10:00 +00:00
if (result.result instanceof Error) {
const error = result.result;
console.error(
`Error from newHeads subscription: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
);
} else {
2018-10-27 17:42:11 +00:00
const block = result.result;
2018-10-27 16:10:00 +00:00
console.log(`New block ${block.number}:`, block);
}
2018-09-10 15:41:17 +00:00
}
});
})
.catch(error => {
console.error(
`Error making newHeads subscription: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
2018-10-05 10:10:36 +00:00
);
2018-09-10 15:41:17 +00:00
});
2019-03-03 13:14:33 +00:00
2019-06-03 18:10:13 +00:00
// Example 4: Log when accounts change
2018-09-10 15:41:17 +00:00
const logAccounts = accounts => {
console.log(`Accounts:\n${accounts.join('\n')}`);
};
ethereum.on('accountsChanged', logAccounts);
// to unsubscribe
ethereum.removeListener('accountsChanged', logAccounts);
2019-06-03 18:10:13 +00:00
// Example 5: Log if connection ends
2018-09-10 15:41:17 +00:00
ethereum.on('close', (code, reason) => {
2018-10-05 10:10:36 +00:00
console.log(`Ethereum provider connection closed: ${reason}. Code: ${code}`);
2018-09-10 15:41:17 +00:00
});
2018-07-17 10:50:16 +00:00
```
## Specification
2019-03-03 13:14:33 +00:00
### Errors
2018-07-17 10:50:16 +00:00
2019-03-03 13:14:33 +00:00
If the Ethereum Provider request returns an error property then the Promise **MUST** reject with an Error object containing the `error.message` as the Error message, `error.code` as a code property on the error and `error.data` as a data property on the error.
2018-07-17 10:50:16 +00:00
2018-09-10 15:41:17 +00:00
If an error occurs during processing, such as an HTTP error or internal parsing error, then the Promise **MUST** reject with an `Error` object.
2018-07-17 10:50:16 +00:00
2019-03-03 13:14:33 +00:00
If the request requires an account that is not yet authenticated, the Promise **MUST** reject with Error code 4100.
2018-09-10 15:41:17 +00:00
2018-10-27 16:10:00 +00:00
### Events
2018-07-17 10:50:16 +00:00
2019-02-06 11:37:11 +00:00
The provider **SHOULD** extend from `EventEmitter` to provide dapps flexibility in listening to events. In place of full `EventEmitter` functionality, the provider **MAY** provide as many methods as it can reasonably provide, but **MUST** provide at least `on` , `emit` , and `removeListener` .
2018-10-27 16:10:00 +00:00
#### notification
2018-07-17 10:50:16 +00:00
2019-03-03 13:14:33 +00:00
All subscriptions received from the node **MUST** emit the `subscription` property with the subscription ID and a `results` property.
2018-07-17 10:50:16 +00:00
2018-10-27 16:10:00 +00:00
#### connect
2018-07-17 10:50:16 +00:00
If the network connects, the Ethereum Provider **MUST** emit an event named `connect` .
2018-10-27 16:10:00 +00:00
#### close
2018-07-23 20:32:56 +00:00
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 ).
2018-07-17 10:50:16 +00:00
2019-06-27 05:45:08 +00:00
#### chainChanged
If the chain the provider is connected to changes, the provider **MUST** emit an event named `chainChanged` with args `chainId: String` containing the ID of the new chain (using the Ethereum JSON-RPC call `eth_chainId` ).
2018-10-27 16:10:00 +00:00
#### networkChanged
2018-07-23 20:32:56 +00:00
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` ).
2018-07-17 10:50:16 +00:00
2018-10-27 16:10:00 +00:00
#### accountsChanged
2019-01-25 21:36:21 +00:00
If the accounts connected to the Ethereum Provider change at any time, the Ethereum Provider **MUST** send an event with the name `accountsChanged` with args `accounts: Array<String>` containing the accounts' addresses.
2018-07-17 10:50:16 +00:00
2018-09-28 22:46:05 +00:00
### web3.js Backwards Compatibility
2018-07-23 20:32:56 +00:00
2019-02-24 22:16:51 +00:00
If the implementing Ethereum Provider would like to be compatible with `web3.js` prior to `1.0.0-beta38` , it **MUST** provide the method: `sendAsync(payload: Object, callback: (error: any, result: any) => void): void` .
2018-07-23 20:32:56 +00:00
### 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.
2018-09-10 15:41:17 +00:00
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:
2019-01-25 21:36:21 +00:00
| 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. |
| 4200 | Unsupported Method | The requested method is not supported by the given Ethereum Provider. |
2018-07-23 20:32:56 +00:00
2018-07-24 02:45:43 +00:00
## Sample Class Implementation
2018-07-23 20:32:56 +00:00
```js
class EthereumProvider extends EventEmitter {
constructor() {
// Call super for `this` to be defined
super();
// Init storage
this._nextJsonrpcId = 0;
this._promises = {};
// 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.');
}
2018-07-24 02:45:43 +00:00
const id = this._nextJsonrpcId++;
const jsonrpc = '2.0';
2018-08-03 11:15:54 +00:00
const payload = { jsonrpc, id, method, params };
2018-07-23 20:32:56 +00:00
const promise = new Promise((resolve, reject) => {
this._promises[payload.id] = { resolve, reject };
});
2018-07-24 02:45:43 +00:00
// Send jsonrpc request to Mist
window.postMessage(
{ type: 'mistAPI_ethereum_provider_write', message: payload },
2018-10-05 06:15:38 +00:00
targetOrigin
2018-07-24 02:45:43 +00:00
);
2018-07-23 20:32:56 +00:00
return promise;
}
/* 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);
}
2018-07-26 19:28:44 +00:00
delete this._promises[id];
2018-07-23 20:32:56 +00:00
}
} else {
if (method & & method.indexOf('_subscription') > -1) {
2018-10-27 16:10:00 +00:00
// Emit subscription notification
this._emitNotification(message.params);
2018-07-23 20:32:56 +00:00
}
}
}
/* Connection handling */
_connect() {
2018-07-24 02:45:43 +00:00
// Send to Mist
2018-10-05 06:15:38 +00:00
window.postMessage(
{ type: 'mistAPI_ethereum_provider_connect' },
targetOrigin
);
2018-07-23 20:32:56 +00:00
// Reconnect on close
this.once('close', this._connect.bind(this));
}
/* Events */
2018-10-27 16:10:00 +00:00
_emitNotification(result) {
this.emit('notification', result);
}
2018-07-23 20:32:56 +00:00
_emitConnect() {
this.emit('connect');
}
_emitClose(code, reason) {
this.emit('close', code, reason);
}
_emitNetworkChanged(networkId) {
this.emit('networkChanged', networkId);
}
_emitAccountsChanged(accounts) {
this.emit('accountsChanged', accounts);
}
2018-09-28 22:46:05 +00:00
/* web3.js Provider Backwards Compatibility */
2018-07-23 20:32:56 +00:00
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}`
);
});
}
}
```
2018-07-17 10:50:16 +00:00
## Copyright
Copyright and related rights waived via [CC0 ](https://creativecommons.org/publicdomain/zero/1.0/ ).