Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update EIP-6963: Editorial changes #7134

Merged
merged 9 commits into from
Jun 5, 2023
308 changes: 172 additions & 136 deletions EIPS/eip-6963.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,21 @@ This is achieved by introducing a set of window events to provide a two-way comm

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.

### Definitions

Wallet: A user agent that manages keys and facilitates transactions with Ethereum.

Decentralized Application (DApp): A web page that relies upon one or many Web3 platform APIs which are exposed to the web page via the Wallet.

Provider Discovery Library: A library or piece of software that assists a DApp to interact with the Wallet.

### Provider Info

Each wallet provider will be announced with the following interface `EIP6963ProviderInfo` that will be used to display to the user:
Each wallet provider will be announced with the following interface `EIP6963ProviderInfo`. The values in the `EIP6963ProviderInfo` MUST be used as follows:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I dropped the walletId property here because UUIDv4 + Wallet Display Name effectively achieves the same goal to uniquely identify the providers I believe. Curious to get some feedback on this change which is the only technical change in this PR at this point. I can split it out into a separate PR as well if it's a bit more controversial.

- **`uuid`** - a globally unique of the wallet provider that MUST be (UUIDv4[^rfc4122] compliant) to uniquely distinguish different [EIP-1193](./eip-1193.md) provider sessions. The cryptographic uniqueness provided by UUIDv4[^rfc4122] guarantees that two independent `EIP6963ProviderInfo` objects can be separately identified.
- **`name`** - A human-readable local alias of the wallet provider to be displayed to the user on the DApp. (e.g. `DopeWalletExtension` or `AwesomeWallet`)
- **`icon`** - A URI[^rfc3986] pointing to an image. Icon images MUST be square with 96x96px minimum resolution

```typescript
/**
Expand All @@ -49,84 +61,28 @@ interface EIP6963ProviderInfo {
}
```

The values in the `EIP6963ProviderInfo` MUST be used as follows:
#### Images/Icons

- **`walletId`** - globally unique identifier of the wallet provider (eg. `io.dopewallet.extension` or `awesomewallet`)
- **`uuid`** - locally unique of the wallet provider (UUIDv4[^rfc4122] compliant)
- **`name`** - human-readable name of the wallet provider (e.g. `DopeWalletExtension` or `AwesomeWallet`)
- **`icon`** - A URI[^rfc3986] pointing to an image. Icon images MUST be square with 96x96px minimum resolution
// TODO for @kdenhartog: This section should be updated to require support of SVGs instead of arbitrary data URIs or URLs that point to images.

[^rfc4122]:
```csl-json
{
"author": [
{
"given": "Paul J.",
"family": "Leach"
},
{
"given": "Rich",
"family": "Salz"
},
{
"given": "Michael H.",
"family": "Mealling"
}
],
"collection-title": "Request for Comments",
"DOI": "10.17487/RFC4122",
"type": "book",
"id": "rfc4122",
"citation-label": "rfc4122",
"issued": {
"date-parts": [
[2005, 7]
]
},
"number-of-pages": "32",
"publisher": "RFC Editor",
"title": "A Universally Unique IDentifier (UUID) URN Namespace",
"URL": "https://www.rfc-editor.org/info/rfc4122"
}
```
A uri encoded image was chosen to enable flexibility for multiple protocols for fetching and rendering icons, for example:

[^rfc3986]:
```csl-json
{
"author": [
{
"given": "Tim",
"family": "Berners-Lee"
},
{
"given": "Roy T.",
"family": "Fielding"
},
{
"given": "Larry M",
"family": "Masinter"
}
],
"collection-title": "Request for Comments",
"DOI": "10.17487/RFC3986",
"type": "book",
"id": "rfc3986",
"citation-label": "rfc3986",
"issued": {
"date-parts": [
[2005, 1]
]
},
"number-of-pages": "61",
"publisher": "RFC Editor",
"title": "Uniform Resource Identifier (URI): Generic Syntax",
"URL": "https://www.rfc-editor.org/info/rfc3986"
}
```
```sh
# svg (data uri)
data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32"><circle fill="red" cx="16" cy="16" r="12"/></svg>
# png (data uri)
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
# png (ipfs uri)
ipfs://QmZo7gsostaUdrV1peFz4cX6Z2TgriFJJP4VahG5knVm29
# webp (http uri)
https://ethereum.org/static/f541df14fca86543040c113725b5bd1a/99bcf/metamask.webp
```

Additionally the image must be squared with 96x96px minimum resolution. Image format is recommended to be lossless like PNG and WebP or alternatively vectorial like SVG. We strongly discourage lossy formats like JPG/JPEG.

### Provider Detail

The wallet provider will also announce their own [EIP-1193](./eip-1193.md) provider interface in parallel with the provider info defined above in the following interface `EIP6963ProviderDetail`
The `EIP6963ProviderDetail` is used as a composition interface to announce a wallet provider and and related metadata about the wallet provider. The `EIP6963ProviderDetail` MUST contain an `info` property of type `EIP6963ProviderInfo` and a `provider` property of type `EIP1193Provider` defined by [EIP-1193](./eip-1193.md).

```typescript
interface EIP6963ProviderDetail {
Expand All @@ -135,101 +91,83 @@ interface EIP6963ProviderDetail {
}
```

The `EIP1193Provider` interface is documented at [EIP-1193](./eip-1193.md) and can be used to override the `window.ethereum` object once the user has explicitly selected it.

### Window Event

Different wallet providers might inject their scripts at different times, plus there is no guarantees that the Ethereum library in the web page will be loaded before all injected scripts are present in the page.
In order to prevent provider collisions, the DApp and the Wallet are expected to emit an event and instantiate an eventListener to discover the various Wallet's. This forms an Event concurrency loop.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While "Wallet" is used as a term, I don't think an apostrophe is needed for pluralizing it


To emit events both Ethereum libraries and Wallet providers must use `window.dispatchEvent` to emit events and `window.addEventListener` to observe events. There will be two event interfaces:
To emit events both DApps and Wallets MUST use the `window.dispatchEvent` function to emit events and MUST use the `window.addEventListener` function to observe events. There will be two Event interfaces used for the DApp and Wallet to discover each other.

```typescript
// Requesting an EVM provider
interface EIP6963RequestProviderEvent extends Event {
type: "eip6963:requestProvider";
}
#### Wallet Events

The `EIP6963AnnounceProviderEvent` interface MUST be a `CustomEvent` object with a `type` property containing a string value of `eip6963:announceProvider` and a `detail` property with an object as it's value of type `EIP6963ProviderDetail`. The `EIP6963ProviderDetail` object MUST be frozen by calling `Object.freeze()` on the value of the `detail` property.

// Annoucing an EVM provider
```typescript
// A Wallet announcing the EIP6963ProviderDetail object
interface EIP6963AnnounceProviderEvent extends CustomEvent {
type: "eip6963:announceProvider";
detail: EIP6963ProviderDetail;
}
```

Therefore both wallet providers and the Ethereum library must emit events when they initialize.
The Wallet must announce to the DApp this `CustomEvent` via a `window.dispatchEvent()` function call. In order for the Wallet to recognize that the DApp is Web3 enabled and can support interactions with the Web3 Platform APIs the Wallet MUST add an EventListener to catch an Event dispatched from the DApp. This EventListener MUST have a `type` property with the string `eip6963:requestProvider` and MUST use a handler that will re-dispatch a `CustomEvent` object of type `EIP6963AnnounceProviderEvent`. This re-announcement by the Wallet is useful for when a Wallet's initial Event announcement may have been delayed or fired before the DApp had initialized it's EventListener. This allows the various Wallet Providers to react to the DApp without the need to pollute the `window.ethereum` namespace which can produce non-deterministic wallet behavior such as different wallets connecting each time.

```typescript
// Ethereum library initializes
window.dispatchEvent(new Event("eip6963:requestProvider"));

// Wallet provider initializes
window.dispatchEvent(new CustomEvent("eip6963:announceProvider", { detail }));
```

Additionally the Wallet providers will react to the event emitted by the Ethereum library.
function announceProvider() {
const info: EIP6963ProviderInfo = {
walletId: "org.example.wallet",
uuid: "350670db-19fa-4704-a166-e52e178b59d2",
name: "Example Wallet",
icon: "https://wallet.example.org/icon.png",
};
window.dispatchEvent(
new CustomEvent("eip6963:announceProvider", {
detail: Object.freeze({ info, provider }),
})
);
}

```typescript
window.addEventListener(
"eip6963:requestProvider",
(event: EIP6963RequestProviderEvent) => {
announceProvider();
}
);
```
#### DApp Events

## Rationale

### Interfaces

Standardizing a provider info interface (`EIP6963ProviderInfo`) allows determining the necessary information to populate a wallet selection popup. This is particularly useful for Ethereum libraries such as Web3Modal, RainbowKit, Web3-Onboard, ConnectKit, etc.
The DApp MUST listen for the `CustomEvent` dispatched by the Wallet via a `window.dispatchEvent()` function call. In order for the DApp to discover the `EIP6963AnnounceProviderEvent` this EventListener MUST have a `type` property with the string `eip6963:requestProvider`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DApp actually listens to the "announce" event:

Suggested change
The DApp MUST listen for the `CustomEvent` dispatched by the Wallet via a `window.dispatchEvent()` function call. In order for the DApp to discover the `EIP6963AnnounceProviderEvent` this EventListener MUST have a `type` property with the string `eip6963:requestProvider`.
The DApp MUST listen for the `CustomEvent` dispatched by the Wallet via a `window.dispatchEvent()` function call. In order for the DApp to discover the `EIP6963AnnounceProviderEvent` this EventListener MUST have a `type` property with the string `eip6963:announceProvider`.


Regarding the announced provider interface (`EIP6963ProviderDetail`) it was important to leave the [EIP-1193](./eip-1193.md) provider interface untouched for backwards-compatiblity therefore it's exposed in parallel.

### Identifiers

A locally unique identifier prevents from conflicts using the same globally unique identifier by using UUID v4.0

A globally unique identifier is used for machine-readable detection of the currently active wallet and it can take different formats, for example:

```text
# lowercase name
awesomewallet
```typescript
// Requesting an EVM provider
interface EIP6963RequestProviderEvent extends Event {
type: "eip6963:requestProvider";
}
```

# legacy JS variable
isCoolWallet
In a DApp's implementation, it uses the `EIP6963RequestProviderEvent` interface. The `EIP6963RequestProviderEvent` interface is an Event object with a `type` property containing a string value of `eip6963:requestProvider`.

# reversed domain
io.dopewallet.app
```typescript
window.addEventListener("eip6963:announceProvider");
```

### Images/Icons
The DApp MAY also keep track of the various `EIP6963ProviderDetail` announced by the various wallets so that if the user wishes to utilize a different Wallet the DApp can immediately send transactions to the new Wallet. Otherwise, the DApp MAY re-initiate the wallet discovery flow via dispatching a new `EIP6963RequestProviderEvent` Event.

A uri encoded image was chosen to enable flexibility for multiple protocols for fetching and rendering icons, for example:
## Rationale

```sh
# svg (data uri)
data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32"><circle fill="red" cx="16" cy="16" r="12"/></svg>
# png (data uri)
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
# png (ipfs uri)
ipfs://QmZo7gsostaUdrV1peFz4cX6Z2TgriFJJP4VahG5knVm29
# webp (http uri)
https://ethereum.org/static/f541df14fca86543040c113725b5bd1a/99bcf/metamask.webp
```
Previous proposal introduced mechanisms that relied on a single window object that could be overridden by multiple parties. Therefore using an event-based approach we avoid the race conditions and potential attacks by making the communication available both ways in a way that doesn't have potential namespace collisions.

Additionally the image must be squared with 96x96px minimum resolution. Image format is recommended to be lossless like PNG and WebP or alternatively vectorial like SVG. We strongly discourage lossy formats like JPG/JPEG.
To follow the Javascript event name conventions, the names are written in present tense and are prefixed with the standard number (`eip6963`).

### Events
### Interfaces

Previous proposal introduced mechanisms that relied on a single window object that could be overriden by multiple parties.
Standardizing a provider info interface (`EIP6963ProviderInfo`) allows determining the necessary information to populate a wallet selection popup. This is particularly useful for DApps and Ethereum libraries they might depend on such as Web3Modal, RainbowKit, Web3-Onboard, ConnectKit, etc.

Therefore using an event-based approach we avoid these race conditions and potential attacks by making the communication available both ways.
Regarding the announced provider interface (`EIP6963ProviderDetail`) it was important to leave the [EIP-1193](./eip-1193.md) provider interface untouched for backwards compatibility therefore it's exposed in parallel. However, just as it is today there is no guarantee that this backwards compatible method will result in the user selected wallet being chosen.

To follow the Javascript event name conventions, the names are written in present tense and are prefixed with the standard number (`eip6963`).

## Backwards Compatibility

This EIP doesn't require supplanting `window.ethereum`, so it doesn't directly break existing applications. However, the recommended behavior of eventually supplanting `window.ethereum` would break existing applications that rely on it.
This EIP doesn't require supplanting `window.ethereum`, so it doesn't directly break existing applications that cannot update to this method of Wallet discovery. However, it is RECOMMENDED DApps implement this EIP and SHOULD disable `window.ethereum` usage to ensure discovery of multiple Wallet Providers. Similarly, Wallets SHOULD keep compatibility of `window.ethereum` to ensure backwards compatibility for DApps that have not implemented this EIP.

## Reference Implementation

Expand Down Expand Up @@ -268,18 +206,17 @@ function onPageLoad() {
}
```

### Ethereum library
### DApp implementation

Here is a reference implementation for a Ethereum library to display and track multiple wallet providers that are injected by browser extensions.
Here is a reference implementation for a DApp to display and track multiple wallet providers that are injected by browser extensions.

```typescript
function onPageLoad() {
const providers: EIP6963ProviderDetail[] = [];
function onPageLoad(): EIP6963ProviderDetail[] => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function onPageLoad(): EIP6963ProviderDetail[] => {
function onPageLoad() => {

Return types are inferred in TypeScript.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shouldn’t it ideally return a cleanup function if anything though? To clean up the addEventListener

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially, but it's not required so that would be up to an implementer to decide if they want to take that approach. My goal with these changes is to rely upon the text to decide what should be done not the examples. The examples, are there more to make the text a bit more clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!


window.addEventListener(
"eip6963:announceProvider",
(event: EIP6963AnnounceProviderEvent) => {
providers.push(event.detail);
providers.push(Object.freeze(event.detail));
}
);

Expand All @@ -293,6 +230,105 @@ The security considerations of [EIP-1193](./eip-1193.md) apply to this EIP.

The use of SVG images introduces a cross-site scripting risk as they can include JavaScript code. Applications and libraries must render SVG images using the `<img>` tag to ensure no JS executions can happen.

// TODO for @kdenhartog: Add a privacy section about Wallet fingerprinting and how Wallets should not announce themselves until the DApp has dispatched the request provider Event and gathered consent from the user that they wish to announce themselves to the DApp.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).


[^rfc4122]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pedrouid do you care how we end up citing these RFCs? With #7113 incoming I think we can do this more easily now using the usual markdown linking normally used.

```csl-json
{
"author": [
{
"given": "Paul J.",
"family": "Leach"
},
{
"given": "Rich",
"family": "Salz"
},
{
"given": "Michael H.",
"family": "Mealling"
}
],
"collection-title": "Request for Comments",
"DOI": "10.17487/RFC4122",
"type": "book",
"id": "rfc4122",
"citation-label": "rfc4122",
"issued": {
"date-parts": [
[2005, 7]
]
},
"number-of-pages": "32",
"publisher": "RFC Editor",
"title": "A Universally Unique IDentifier (UUID) URN Namespace",
"URL": "https://www.rfc-editor.org/info/rfc4122"
}
```

[^rfc3986]:
```csl-json
{
"author": [
{
"given": "Tim",
"family": "Berners-Lee"
},
{
"given": "Roy T.",
"family": "Fielding"
},
{
"given": "Larry M",
"family": "Masinter"
}
],
"collection-title": "Request for Comments",
"DOI": "10.17487/RFC3986",
"type": "book",
"id": "rfc3986",
"citation-label": "rfc3986",
"issued": {
"date-parts": [
[2005, 1]
]
},
"number-of-pages": "61",
"publisher": "RFC Editor",
"title": "Uniform Resource Identifier (URI): Generic Syntax",
"URL": "https://www.rfc-editor.org/info/rfc3986"
}
```

[^rfc2397]:
```csl-json
{
"type": "book",
"id": "RFC2397",
"author": [
{
"family": "Hoffman",
"given": "Paul"
}
],
"collection-title": "Request for Comments",
"DOI": "10.17487/RFC2397", // TODO determine proper DOI number
"type": "book",
"id": "rfc2397",
"citation-label": "rfc2397",
"issued": {
"date-parts": [
[1998, 08]
]
},
"number-of-pages": "5",
"publisher": "RFC Editor",
"title": "The 'data' URL scheme",
"URL": "https://www.rfc-editor.org/info/rfc3986"
}
```