diff --git a/EIPS/eip-1193.md b/EIPS/eip-1193.md index 75510c9a..5fee67e9 100644 --- a/EIPS/eip-1193.md +++ b/EIPS/eip-1193.md @@ -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; -``` - -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) => 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`, 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; +``` + +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; +Provider.send(...args: unknown[]): unknown; ``` ### Legacy Events