---
eip: 1193
title: Ethereum Provider JavaScript API
author: Fabian Vogelsteller (@frozeman), Ryan Ghods (@ryanio), Marc Garreau (@marcgarreau), Victor Maia (@MaiaVictor)
discussions-to: https://ethereum-magicians.org/t/eip-1193-ethereum-provider-javascript-api/640
status: Draft
type: Standards Track
category: Interface
created: 2018-06-30
---

## Summary

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.

## API

### Send

Ethereum API methods can be sent and received:

```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).

### Events

Events are emitted using [EventEmitter](https://nodejs.org/api/events.html).

#### notification

All subscriptions from the node emit on notification. Attach listeners with:

```js
ethereum.on('notification', listener: (result: any) => void): this;
```

To create a subscription, call `ethereum.send('eth_subscribe', [])` or `ethereum.send('shh_subscribe', [])`. 

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

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<String>) => void): this;
```

The event emits with `accounts`, an array of the accounts' addresses.

## Examples

```js
const ethereum = window.ethereum;

// A) Set provider in web3.js
var web3 = new Web3(ethereum);
// web3.eth.getBlock('latest', true).then(...)


// B) Use provider object directly
// Example 1: Log last block
ethereum
  .send('eth_getBlockByNumber', ['latest', 'true'])
  .then(block => {
    console.log(`Block ${block.number}:`, block);
  })
  .catch(error => {
    console.error(
      `Error fetching last block: ${error.message}.
       Code: ${error.code}. Data: ${error.data}`
    );
  });

// Example 2: 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 3: Log new blocks
let subId;
ethereum
  .send('eth_subscribe', ['newHeads'])
  .then(subscriptionId => {
    subId = subscriptionId;
    ethereum.on('notification', result => {
      if (result.subscription === 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 {
          const block = result.result;
          console.log(`New block ${block.number}:`, block);
        }
      }
    });
  })
  .catch(error => {
    console.error(
      `Error making newHeads subscription: ${error.message}.
       Code: ${error.code}. Data: ${error.data}`
    );
  });


// Example 4: 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 5: Log if connection ends
ethereum.on('close', (code, reason) => {
  console.log(`Ethereum provider connection closed: ${reason}. Code: ${code}`);
});
```

## Specification

### Errors

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.

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 request requires an account that is not yet authenticated, the Promise **MUST** reject with Error code 4100.

### Events

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`.

#### notification

All subscriptions received from the node **MUST** emit the `subscription` property with the subscription ID and a `results` property.

#### 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 at any time, the Ethereum Provider **MUST** send an event with the name `accountsChanged` with args `accounts: Array<String>` containing the accounts' addresses.

### web3.js Backwards Compatibility

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`.

### 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), along with the following table:

| 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. |

## Sample Class Implementation

```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.');
    }

    const id = this._nextJsonrpcId++;
    const jsonrpc = '2.0';
    const payload = { jsonrpc, id, method, params };

    const promise = new Promise((resolve, reject) => {
      this._promises[payload.id] = { resolve, reject };
    });

    // Send jsonrpc request to Mist
    window.postMessage(
      { type: 'mistAPI_ethereum_provider_write', message: payload },
      targetOrigin
    );

    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);
        }
        delete this._promises[id];
      }
    } else {
      if (method && method.indexOf('_subscription') > -1) {
        // Emit subscription notification
        this._emitNotification(message.params);
      }
    }
  }

  /* Connection handling */

  _connect() {
    // Send to Mist
    window.postMessage(
      { type: 'mistAPI_ethereum_provider_connect' },
      targetOrigin
    );

    // Reconnect on close
    this.once('close', this._connect.bind(this));
  }

  /* Events */

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

  _emitConnect() {
    this.emit('connect');
  }

  _emitClose(code, reason) {
    this.emit('close', code, reason);
  }

  _emitNetworkChanged(networkId) {
    this.emit('networkChanged', networkId);
  }

  _emitAccountsChanged(accounts) {
    this.emit('accountsChanged', accounts);
  }

  /* web3.js Provider Backwards 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}`
        );
      });
  }
}
```

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).