Automatically merged updates to draft EIP(s) 1193 (#2702)

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:
Erik Marks 2020-06-04 20:36:24 -07:00 committed by GitHub
parent c685eca70a
commit c90b98e2df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -13,219 +13,20 @@ requires: 155, 695
## Summary
This EIP formalizes a JavaScript Ethereum Provider API for consistency across clients and applications.
A JavaScript Ethereum Provider API for consistency across clients and applications.
The Provider's interface is designed to be minimal, event-driven, and agnostic of transport and RPC protocols.
New functionality is best introduced via new RPC methods.
## 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.
## API
### request
Makes an Ethereum RPC method call.
```typescript
interface RequestArguments {
method: string;
params?: 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](https://eips.ethereum.org/EIPS/eip-1474).
#### RPC Protocols
Multiple RPC protocols may be available. For examples, see:
- [EIP-1474](https://eips.ethereum.org/EIPS/eip-1474), the Ethereum JSON-RPC API
- [EIP-1767](https://eips.ethereum.org/EIPS/eip-1767), the Ethereum GraphQL schema
### Events
Events follow the [Node.js `EventEmitter`](https://nodejs.org/api/events.html) API.
#### 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 {
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: Array<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 {
type: string;
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;
}
```
## Examples
> These examples assume a web browser environment.
```javascript
// The Provider will usually be 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 (typeof data.result === 'string' && data.result) {
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}`);
});
```
## 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).
@ -259,9 +60,10 @@ The Provider is said to be "disconnected" when it cannot service RPC requests to
> 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.
>
> The authors recommend that new functionality be introduced via new RPC methods instead of extensions of this interface, to the greatest extent possible.
> 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.
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
@ -360,7 +162,6 @@ interface ProviderRpcError extends Error {
The Provider **MUST** implement the following event handling methods:
- `on`
- `emit`
- `removeListener`
These methods **MUST** be implemented per the Node.js [`EventEmitter` API](https://nodejs.org/api/events.html).
@ -380,9 +181,9 @@ interface ProviderMessage {
}
```
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.
##### Subscriptions
##### Converting a Subscription Message to a ProviderMessage
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:
@ -429,13 +230,51 @@ If the chain the Provider is connected to changes, the Provider **MUST** emit th
#### accountsChanged
If the accounts available to the Provider change, the Provider **MUST** emit the event named `accountsChanged` with value `accounts: Array<string>`, containing the account addresses per the `eth_accounts` Ethereum RPC method.
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.
## Security Considerations
## Rationale
_This section and its sub-sections are non-normative._
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)
- [MetaMask](https://github.com/MetaMask/inpage-provider)
- [web3.js](https://web3js.readthedocs.io/)
- [WalletConnect](https://github.com/WalletConnect/walletconnect-monorepo/blob/master/packages/providers/web3-provider/)
## 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.
@ -469,7 +308,7 @@ As with `eth_chainId`, it is critical that `eth_accounts` has the correct return
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](https://eips.ethereum.org/EIPS/eip-1102)) or `wallet_requestPermissions` (see [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255).
Instead, Providers should support RPC methods for explicitly requesting account access, such as `eth_requestAccounts` (see [EIP-1102](https://eips.ethereum.org/EIPS/eip-1102)) or `wallet_requestPermissions` (see [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255)).
## References
@ -477,16 +316,222 @@ Instead, Providers should support RPC methods for explicitly requesting account
- [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](https://eips.ethereum.org/EIPS/eip-1102)
- [EIP-1474](https://eips.ethereum.org/EIPS/eip-1474)
- [EIP-1767](https://eips.ethereum.org/EIPS/eip-1767)
- [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255)
- [EIP-1102: Opt-in Account Exposure](https://eips.ethereum.org/EIPS/eip-1102)
- [EIP-1474: Remote Procedure Call Specification](https://eips.ethereum.org/EIPS/eip-1474)
- [EIP-1767: GraphQL Interface to Ethereum Node Data](https://eips.ethereum.org/EIPS/eip-1767)
- [EIP-2255: Wallet Permissions](https://eips.ethereum.org/EIPS/eip-2255)
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
## Appendix I: Legacy Provider API
## Appendix I: Consumer-Facing API Documentation
### request
Makes an Ethereum RPC method call.
```typescript
interface RequestArguments {
method: string;
params?: 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](https://eips.ethereum.org/EIPS/eip-1474).
#### RPC Protocols
Multiple RPC protocols may be available. For examples, see:
- [EIP-1474](https://eips.ethereum.org/EIPS/eip-1474), the Ethereum JSON-RPC API
- [EIP-1767](https://eips.ethereum.org/EIPS/eip-1767), 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 {
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 {
type: string;
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 (typeof data.result === 'string' && data.result) {
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.
@ -509,7 +554,7 @@ Historically, the request and response object interfaces have followed the [Ethe
This method is superseded by [`request`](#request).
```typescript
Provider.send(...args: Array<unknown>): unknown;
Provider.send(...args: unknown[]): unknown;
```
### Legacy Events