DCIPs/EIPS/eip-1193.md

577 lines
23 KiB
Markdown
Raw Normal View History

---
eip: 1193
title: Ethereum Provider JavaScript API
author: Fabian Vogelsteller (@frozeman), Ryan Ghods (@ryanio), Victor Maia (@MaiaVictor), Marc Garreau (@marcgarreau), Erik Marks (@rekmarks)
discussions-to: https://github.com/ethereum/EIPs/issues/2319
status: Final
type: Standards Track
category: Interface
created: 2018-06-30
requires: 155, 695
---
## Summary
A JavaScript Ethereum Provider API for consistency across clients and applications.
## Abstract
A common convention in the Ethereum web application ("dapp") ecosystem is for key management software ("wallets") to expose their API via a JavaScript object in the web page.
This object is called "the Provider".
Historically, Provider implementations have exhibited conflicting interfaces and behaviors between wallets.
This EIP formalizes an Ethereum Provider API to promote wallet interoperability.
The API is designed to be minimal, event-driven, and agnostic of transport and RPC protocols.
Its functionality is easily extended by defining new RPC methods and `message` event types.
Historically, Providers have been made available as `window.ethereum` in web browsers, but this convention is not part of the specification.
## Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC-2119](https://www.ietf.org/rfc/rfc2119.txt).
> Comments like this are non-normative.
### Definitions
_This section is non-normative._
- Provider
- A JavaScript object made available to a consumer, that provides access to Ethereum by means of a Client.
- Client
- An endpoint that receives Remote Procedure Call (RPC) requests from the Provider, and returns their results.
- Wallet
- An end-user application that manages private keys, performs signing operations, and acts as a middleware between the Provider and the Client.
- Remote Procedure Call (RPC)
- A Remote Procedure Call (RPC), is any request submitted to a Provider for some procedure that is to be processed by a Provider, its Wallet, or its Client.
### Connectivity
The Provider is said to be "connected" when it can service RPC requests to at least one chain.
The Provider is said to be "disconnected" when it cannot service RPC requests to any chain at all.
> To service an RPC request, the Provider must successfully submit the request to the remote location, and receive a response.
> In other words, if the Provider is unable to communicate with its Client, for example due to network issues, the Provider is disconnected.
### API
> The Provider API is specified using TypeScript.
> The authors encourage implementers to declare their own types and interfaces, using the ones in this section as a basis.
>
> For consumer-facing API documentation, see [Appendix I](#appendix-i-consumer-facing-api-documentation)
The Provider **MUST** implement and expose the API defined in this section.
All API entities **MUST** adhere to the types and interfaces defined in this section.
#### request
> The `request` method is intended as a transport- and protocol-agnostic wrapper function for Remote Procedure Calls (RPCs).
```typescript
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}
Provider.request(args: RequestArguments): Promise<unknown>;
```
The Provider **MUST** identify the requested RPC method by the value of `RequestArguments.method`.
If the requested RPC method takes any parameters, the Provider **MUST** accept them as the value of `RequestArguments.params`.
RPC requests **MUST** be handled such that the returned Promise either resolves with a value per the requested RPC method's specification, or rejects with an error.
If resolved, the Promise **MUST** resolve with a result per the RPC method's specification. The Promise **MUST NOT** resolve with any RPC protocol-specific response objects, unless the RPC method's return type is so defined.
If the returned Promise rejects, it **MUST** reject with a `ProviderRpcError` as specified in the [RPC Errors](#rpc-errors) section below.
The returned Promise **MUST** reject if any of the following conditions are met:
- An error is returned for the RPC request.
- If the returned error is compatible with the `ProviderRpcError` interface, the Promise **MAY** reject with that error directly.
- The Provider encounters an error or fails to process the request for any reason.
> If the Provider implements any kind of authorization logic, the authors recommend rejecting with a `4100` error in case of authorization failures.
The returned Promise **SHOULD** reject if any of the following conditions are met:
- The Provider is disconnected.
- If rejecting for this reason, the Promise rejection error `code` **MUST** be `4900`.
- The RPC request is directed at a specific chain, and the Provider is not connected to that chain, but is connected to at least one other chain.
- If rejecting for this reason, the Promise rejection error `code` **MUST** be `4901`.
See the section [Connectivity](#connectivity) for the definitions of "connected" and "disconnected".
### Supported RPC Methods
A "supported RPC method" is any RPC method that may be called via the Provider.
All supported RPC methods **MUST** be identified by unique strings.
Providers **MAY** support whatever RPC methods required to fulfill their purpose, standardized or otherwise.
If an RPC method defined in a finalized EIP is not supported, it **SHOULD** be rejected with a `4200` error per the [Provider Errors](#provider-errors) section below, or an appropriate error per the RPC method's specification.
#### RPC Errors
```typescript
interface ProviderRpcError extends Error {
code: number;
data?: unknown;
}
```
- `message`
- **MUST** be a human-readable string
- **SHOULD** adhere to the specifications in the [Error Standards](#error-standards) section below
- `code`
- **MUST** be an integer number
- **SHOULD** adhere to the specifications in the [Error Standards](#error-standards) section below
- `data`
- **SHOULD** contain any other useful information about the error
##### Error Standards
`ProviderRpcError` codes and messages **SHOULD** follow these conventions, in order of priority:
1. The errors in the [Provider Errors](#provider-errors) section below
2. Any errors mandated by the erroring RPC method's specification
3. The [`CloseEvent` status codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes)
#### Provider Errors
| Status code | Name | Description |
| ----------- | --------------------- | ------------------------------------------------------------------------ |
| 4001 | User Rejected Request | The user rejected the request. |
| 4100 | Unauthorized | The requested method and/or account has not been authorized by the user. |
| 4200 | Unsupported Method | The Provider does not support the requested method. |
| 4900 | Disconnected | The Provider is disconnected from all chains. |
| 4901 | Chain Disconnected | The Provider is not connected to the requested chain. |
> `4900` is intended to indicate that the Provider is disconnected from all chains, while `4901` is intended to indicate that the Provider is disconnected from a specific chain only.
> In other words, `4901` implies that the Provider is connected to other chains, just not the requested one.
### Events
The Provider **MUST** implement the following event handling methods:
- `on`
- `removeListener`
These methods **MUST** be implemented per the Node.js [`EventEmitter` API](https://nodejs.org/api/events.html).
> To satisfy these requirements, Provider implementers should consider simply extending the Node.js `EventEmitter` class and bundling it for the target environment.
#### message
> The `message` event is intended for arbitrary notifications not covered by other events.
When emitted, the `message` event **MUST** be emitted with an object argument of the following form:
```typescript
interface ProviderMessage {
readonly type: string;
readonly data: unknown;
}
```
##### Subscriptions
If the Provider supports Ethereum RPC subscriptions, e.g. [`eth_subscribe`](https://geth.ethereum.org/docs/rpc/pubsub), the Provider **MUST** emit the `message` event when it receives a subscription notification.
If the Provider receives a subscription message from e.g. an `eth_subscribe` subscription, the Provider **MUST** emit a `message` event with a `ProviderMessage` object of the following form:
```typescript
interface EthSubscription extends ProviderMessage {
readonly type: 'eth_subscription';
readonly data: {
readonly subscription: string;
readonly result: unknown;
};
}
```
#### connect
See the section [Connectivity](#connectivity) for the definition of "connected".
If the Provider becomes connected, the Provider **MUST** emit the event named `connect`.
This includes when:
- The Provider first connects to a chain after initialization.
- The Provider connects to a chain after the `disconnect` event was emitted.
This event **MUST** be emitted with an object of the following form:
```typescript
interface ProviderConnectInfo {
readonly chainId: string;
}
```
`chainId` **MUST** specify the integer ID of the connected chain as a hexadecimal string, per the [`eth_chainId`](./eip-695.md) Ethereum RPC method.
#### disconnect
See the section [Connectivity](#connectivity) for the definition of "disconnected".
If the Provider becomes disconnected from all chains, the Provider **MUST** emit the event named `disconnect` with value `error: ProviderRpcError`, per the interfaced defined in the [RPC Errors](#rpc-errors) section. The value of the error's `code` property **MUST** follow the [status codes for `CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes).
#### chainChanged
If the chain the Provider is connected to changes, the Provider **MUST** emit the event named `chainChanged` with value `chainId: string`, specifying the integer ID of the new chain as a hexadecimal string, per the [`eth_chainId`](./eip-695.md) Ethereum RPC method.
#### accountsChanged
If the accounts available to the Provider change, the Provider **MUST** emit the event named `accountsChanged` with value `accounts: string[]`, containing the account addresses per the `eth_accounts` Ethereum RPC method.
The "accounts available to the Provider" change when the return value of `eth_accounts` changes.
## Rationale
The purpose of a Provider is to _provide_ a consumer with access to Ethereum.
In general, a Provider must enable an Ethereum web application to do two things:
- Make Ethereum RPC requests
- Respond to state changes in the Provider's Ethereum chain, Client, and Wallet
The Provider API specification consists of a single method and five events.
The `request` method and the `message` event alone, are sufficient to implement a complete Provider.
They are designed to make arbitrary RPC requests and communicate arbitrary messages, respectively.
The remaining four events can be separated into two categories:
- Changes to the Provider's ability to make RPC requests
- `connect`
- `disconnect`
- Common Client and/or Wallet state changes that any non-trivial application must handle
- `chainChanged`
- `accountsChanged`
These events are included due to the widespread production usage of related patterns, at the time of writing.
## Backwards Compatibility
Many Providers adopted a draft version of this specification before it was finalized.
The current API is designed to be a strict superset of the legacy version, and this specification is in that sense fully backwards compatible.
See [Appendix III](#appendix-iii-legacy-provider-api) for the legacy API.
Providers that only implement this specification will not be compatible with Ethereum web applications that target the legacy API.
## Implementations
At the time of writing, the following projects have working implementations:
- [buidler.dev](https://github.com/nomiclabs/buidler/pull/608)
- [ethers.js](https://github.com/ethers-io/ethers.js/blob/56af4413b1dd1787db68985e0b612b63d86fdf7c/packages/providers/src.ts/web3-provider.ts)
- [eth-provider](https://www.npmjs.com/package/eth-provider)
- [MetaMask](https://github.com/MetaMask/inpage-provider)
- [WalletConnect](https://github.com/WalletConnect/walletconnect-monorepo/blob/d33fd2070d7a67f74de50fd10ca4217f4e2f22f3/packages/providers/web3-provider/README.md)
- [web3.js](https://web3js.readthedocs.io/)
## Security Considerations
The Provider is intended to pass messages between an Ethereum Client and an Ethereum application.
It is _not_ responsible for private key or account management; it merely processes RPC messages and emits events.
Consequently, account security and user privacy need to be implemented in middlewares between the Provider and its Ethereum Client.
In practice, we call these middleware applications "Wallets," and they usually manage the user's private keys and accounts.
The Provider can be thought of as an extension of the Wallet, exposed in an untrusted environment, under the control of some third party (e.g. a website).
### Handling Adversarial Behavior
Since it is a JavaScript object, consumers can generally perform arbitrary operations on the Provider, and all its properties can be read or overwritten.
Therefore, it is best to treat the Provider object as though it is controlled by an adversary.
It is paramount that the Provider implementer protects the user, Wallet, and Client by ensuring that:
- The Provider does not contain any private user data.
- The Provider and Wallet programs are isolated from each other.
- The Wallet and/or Client rate-limit requests from the Provider.
- The Wallet and/or Client validate all data sent from the Provider.
### Chain Changes
Since all Ethereum operations are directed at a particular chain, it's important that the Provider accurately reflects the Client's configured chain, per the `eth_chainId` Ethereum RPC method (see [EIP-695](./eip-695.md)).
This includes ensuring that `eth_chainId` has the correct return value, and that the `chainChanged` event is emitted whenever that value changes.
### User Account Exposure and Account Changes
Many Ethereum write operations (e.g. `eth_sendTransaction`) require a user account to be specified.
Provider consumers access these accounts via the `eth_accounts` RPC method, and by listening for the `accountsChanged` event.
As with `eth_chainId`, it is critical that `eth_accounts` has the correct return value, and that the `accountsChanged` event is emitted whenever that value changes.
The return value of `eth_accounts` is ultimately controlled by the Wallet or Client.
In order to protect user privacy, the authors recommend not exposing any accounts by default.
Instead, Providers should support RPC methods for explicitly requesting account access, such as `eth_requestAccounts` (see [EIP-1102](./eip-1102.md)) or `wallet_requestPermissions` (see [EIP-2255](./eip-2255.md)).
## References
- [Initial discussion in `ethereum/interfaces`](https://github.com/ethereum/interfaces/issues/16)
- [Deprecated Ethereum Magicians thread](https://ethereum-magicians.org/t/eip-1193-ethereum-provider-javascript-api/640)
- [Continuing discussion](https://github.com/ethereum/EIPs/issues/2319)
- Related EIPs
- [EIP-1102: Opt-in Account Exposure](./eip-1102.md)
- [EIP-1474: Remote Procedure Call Specification](./eip-1474.md)
- [EIP-1767: GraphQL Interface to Ethereum Node Data](./eip-1767.md)
- [EIP-2255: Wallet Permissions](./eip-2255.md)
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).
## Appendix I: Consumer-Facing API Documentation
### request
Makes an Ethereum RPC method call.
```typescript
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}
Provider.request(args: RequestArguments): Promise<unknown>;
```
The returned Promise resolves with the method's result or rejects with a [`ProviderRpcError`](#errors). For example:
```javascript
Provider.request({ method: 'eth_accounts' })
.then((accounts) => console.log(accounts))
.catch((error) => console.error(error));
```
Consult each Ethereum RPC method's documentation for its `params` and return type.
You can find a list of common methods [here](./eip-1474.md).
#### RPC Protocols
Multiple RPC protocols may be available. For examples, see:
- [EIP-1474](./eip-1474.md), the Ethereum JSON-RPC API
- [EIP-1767](./eip-1767.md), the Ethereum GraphQL schema
### Events
Events follow the conventions of the Node.js [`EventEmitter` API](https://nodejs.org/api/events.html).
#### connect
The Provider emits `connect` when it:
- first connects to a chain after being initialized.
- first connects to a chain, after the `disconnect` event was emitted.
```typescript
interface ProviderConnectInfo {
readonly chainId: string;
}
Provider.on('connect', listener: (connectInfo: ProviderConnectInfo) => void): Provider;
```
The event emits an object with a hexadecimal string `chainId` per the `eth_chainId` Ethereum RPC method, and other properties as determined by the Provider.
#### disconnect
The Provider emits `disconnect` when it becomes disconnected from all chains.
```typescript
Provider.on('disconnect', listener: (error: ProviderRpcError) => void): Provider;
```
This event emits a [`ProviderRpcError`](#errors). The error `code` follows the table of [`CloseEvent` status codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes).
#### chainChanged
The Provider emits `chainChanged` when connecting to a new chain.
```typescript
Provider.on('chainChanged', listener: (chainId: string) => void): Provider;
```
The event emits a hexadecimal string `chainId` per the `eth_chainId` Ethereum RPC method.
#### accountsChanged
The Provider emits `accountsChanged` if the accounts returned from the Provider (`eth_accounts`) change.
```typescript
Provider.on('accountsChanged', listener: (accounts: string[]) => void): Provider;
```
The event emits with `accounts`, an array of account addresses, per the `eth_accounts` Ethereum RPC method.
#### message
The Provider emits `message` to communicate arbitrary messages to the consumer.
Messages may include JSON-RPC notifications, GraphQL subscriptions, and/or any other event as defined by the Provider.
```typescript
interface ProviderMessage {
readonly type: string;
readonly data: unknown;
}
Provider.on('message', listener: (message: ProviderMessage) => void): Provider;
```
##### Subscriptions
[`eth_` subscription methods](https://geth.ethereum.org/docs/rpc/pubsub) and [`shh_` subscription methods](https://github.com/ethereum/go-ethereum/wiki/Whisper-v6-RPC-API#shh_subscribe) rely on this event to emit subscription updates.
For e.g. `eth_subscribe` subscription updates, `ProviderMessage.type` will equal the string `'eth_subscription'`, and the subscription data will be the value of `ProviderMessage.data`.
### Errors
```typescript
interface ProviderRpcError extends Error {
message: string;
code: number;
data?: unknown;
}
```
## Appendix II: Examples
These examples assume a web browser environment.
```javascript
// Most Providers are available as window.ethereum on page load.
// This is only a convention, not a standard, and may not be the case in practice.
// Please consult the Provider implementation's documentation.
const ethereum = window.ethereum;
// Example 1: Log chainId
ethereum
.request({ method: 'eth_chainId' })
.then((chainId) => {
console.log(`hexadecimal string: ${chainId}`);
console.log(`decimal number: ${parseInt(chainId, 16)}`);
})
.catch((error) => {
console.error(`Error fetching chainId: ${error.code}: ${error.message}`);
});
// Example 2: Log last block
ethereum
.request({
method: 'eth_getBlockByNumber',
params: ['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 3: Log available accounts
ethereum
.request({ method: '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
ethereum
.request({
method: 'eth_subscribe',
params: ['newHeads'],
})
.then((subscriptionId) => {
ethereum.on('message', (message) => {
if (message.type === 'eth_subscription') {
const { data } = message;
if (data.subscription === subscriptionId) {
if ('result' in data && typeof data.result === 'object') {
const block = data.result;
console.log(`New block ${block.number}:`, block);
} else {
console.error(`Something went wrong: ${data.result}`);
}
}
}
});
})
.catch((error) => {
console.error(
`Error making newHeads subscription: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
);
});
// Example 5: 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 6: Log if connection ends
ethereum.on('disconnect', (code, reason) => {
console.log(`Ethereum Provider connection closed: ${reason}. Code: ${code}`);
});
```
## Appendix III: Legacy Provider API
This section documents the legacy Provider API, which is extensively used in production at the time of writing.
As it was never fully standardized, significant deviations occur in practice.
The authors recommend against implementing it except to support legacy Ethereum applications.
### sendAsync (DEPRECATED)
This method is superseded by [`request`](#request).
`sendAsync` is like `request`, but with JSON-RPC objects and a callback.
```typescript
Provider.sendAsync(request: Object, callback: Function): void;
```
Historically, the request and response object interfaces have followed the [Ethereum JSON-RPC specification](./eip-1474.md).
### send (DEPRECATED)
This method is superseded by [`request`](#request).
```typescript
Provider.send(...args: unknown[]): unknown;
```
### Legacy Events
#### close (DEPRECATED)
This event is superseded by [`disconnect`](#disconnect).
#### networkChanged (DEPRECATED)
The event `networkChanged` is superseded by [`chainChanged`](#chainchanged).
For details, see [EIP-155: Simple replay attack protection](./eip-155.md) and [EIP-695: Create eth_chainId method for JSON-RPC](./eip-695.md).
#### notification (DEPRECATED)
This event is superseded by [`message`](#message).
Historically, this event has been emitted with e.g. `eth_subscribe` subscription updates of the form `{ subscription: string, result: unknown }`.