Skip to content

Commit

Permalink
6201 multi provider (#6825)
Browse files Browse the repository at this point in the history
* requestEIP6963Providers function

* requestEIP6963Providers in web3 class

* changelog update

* unit test

* removed unused file

* lint fix

* added doc

---------

Co-authored-by: Oleksii Kosynskyi <oleksii.kosynskyi@gmail.com>
  • Loading branch information
jdevcs and avkos committed Feb 27, 2024
1 parent b4c92e1 commit 86447cd
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 44 deletions.
58 changes: 58 additions & 0 deletions docs/docs/guides/web3_providers_guide/eip6963.md
@@ -0,0 +1,58 @@
---
sidebar_position: 2
sidebar_label: 'EIP-6963: Multi Injected Provider Discovery'
---

# EIP-6963: Multi Injected Provider Discovery

## Introduction

EIP-6963 proposes the "Multi Injected Provider Discovery" standard, which aims to enhance the discoverability and interaction with multiple injected Ethereum providers in a browser environment. Injected providers refer to browser extensions or other injected scripts that provide access to an Ethereum provider within the context of a web application.

Web3.js library has utility function for discovery of injected providers using `requestEIP6963Providers()` function. When `requestEIP6963Providers()` is used it returns `eip6963Providers` Map object. This Map object is in global scope so every time `requestEIP6963Providers()` function is called it will update Map object and return it.

`eip6963Providers` Map object has provider's `UUID` as keys and `EIP6963ProviderDetail` as values. `EIP6963ProviderDetail` is:

```ts
export interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo;
provider: EIP1193Provider;
}
```

where `info` has details of provider containing UUID, name, Icon and RDNS as defined in EIP-6963:

```ts
export interface EIP6963ProviderInfo {
uuid: string;
name: string;
icon: string;
rdns: string;
}
```

`provider` in `EIP6963ProviderDetail` is `EIP1193Provider` and it contains actual provider that can be injected in web3 instance.

Following code snippet demonstrates usage of `requestEIP6963Providers()` function for providers discovery.

```ts
//Assuming multiple providers are installed in browser.

import { Web3 } from 'web3';

const providers = Web3.requestEIP6963Providers();

for (const [key, value] of providers) {
console.log(value);

/* Based on your DApp's logic show use list of providers and get selected provider's UUID from user for injecting its EIP6963ProviderDetail.provider EIP1193 object into web3 object */

if (value.info.name === 'MetaMask') {
const web3 = new Web3(value.provider);

// now you can use web3 object with injected provider
console.log(await web3.eth.getTransaction('0x82512812c11f56aa2474a16d5cc8916b73cd6ed96bf9b8defb3499ec2d9070cb'));
}
}

```
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/events_listening.md
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
sidebar_label: 'Providers Events Listening'
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/http.md
@@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
sidebar_label: 'Tutorial: HTTP Provider'
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/injected_provider.md
@@ -1,5 +1,5 @@
---
sidebar_position: 6
sidebar_position: 7
sidebar_label: 'Tutorial: Injected provider'
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/ipc.md
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 6
sidebar_label: 'Tutorial: IPC Provider'
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/truffle.md
@@ -1,5 +1,5 @@
---
sidebar_position: 7
sidebar_position: 8
sidebar_label: 'Tutorial: Third Party Provider'
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/web3_providers_guide/websocket.md
@@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
sidebar_label: 'Tutorial: WebSocket Provider'
---

Expand Down
6 changes: 5 additions & 1 deletion packages/web3/CHANGELOG.md
Expand Up @@ -191,4 +191,8 @@ Documentation:

- Dependencies updated ( details are in root changelog )

## [Unreleased]
## [Unreleased]

### Added

- Added EIP-6963 utility function `requestEIP6963Providers` for multi provider discovery
2 changes: 2 additions & 0 deletions packages/web3/src/web3.ts
Expand Up @@ -44,6 +44,7 @@ import abi from './abi.js';
import { initAccountsForContext } from './accounts.js';
import { Web3EthInterface } from './types.js';
import { Web3PkgInfo } from './version.js';
import { requestEIP6963Providers } from './web3_eip6963.js';

export class Web3<
CustomRegisteredSubscription extends {
Expand All @@ -52,6 +53,7 @@ export class Web3<
> extends Web3Context<EthExecutionAPI, CustomRegisteredSubscription & RegisteredSubscription> {
public static version = Web3PkgInfo.version;
public static utils = utils;
public static requestEIP6963Providers = requestEIP6963Providers;
public static modules = {
Web3Eth,
Iban,
Expand Down
71 changes: 71 additions & 0 deletions packages/web3/src/web3_eip6963.ts
@@ -0,0 +1,71 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { Web3APISpec, EIP1193Provider } from "web3-types";


export enum Eip6963EventName {
eip6963announceProvider = 'eip6963:announceProvider',
eip6963requestProvider = 'eip6963:requestProvider',
};

export interface EIP6963ProviderInfo {
uuid: string;
name: string;
icon: string;
rdns: string;
}

export interface EIP6963ProviderDetail<API = Web3APISpec> {
info: EIP6963ProviderInfo;
provider: EIP1193Provider<API>;
}

export interface EIP6963AnnounceProviderEvent<API = Web3APISpec> extends CustomEvent {
type: Eip6963EventName.eip6963announceProvider;
detail: EIP6963ProviderDetail<API>;
}

export interface EIP6963RequestProviderEvent extends Event {
type: Eip6963EventName.eip6963requestProvider;
}

export const eip6963Providers: Map<string, EIP6963ProviderDetail> = new Map();

export const requestEIP6963Providers = () => {

if (typeof window === 'undefined')
throw new Error(
"window object not available, EIP-6963 is intended to be used within a browser"
);

window.addEventListener(
Eip6963EventName.eip6963announceProvider as any,
(event: EIP6963AnnounceProviderEvent) => {

eip6963Providers.set(
event.detail.info.uuid,
event.detail);
}
);

window.dispatchEvent(new Event(Eip6963EventName.eip6963requestProvider));

return eip6963Providers;
}


37 changes: 0 additions & 37 deletions packages/web3/test/unit/constructor.test.ts

This file was deleted.

86 changes: 86 additions & 0 deletions packages/web3/test/unit/web3eip6963.test.ts
@@ -0,0 +1,86 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import {
EIP6963AnnounceProviderEvent,
EIP6963ProviderDetail,
Eip6963EventName,
eip6963Providers,
requestEIP6963Providers
} from "../../src/web3_eip6963";

describe('requestEIP6963Providers', () => {

it('should request EIP6963 providers and store them in eip6963Providers', () => {

const mockProviderDetail: EIP6963ProviderDetail = {
info: {
uuid: '1',
name: 'MockProvider',
icon: 'icon-path',
rdns: 'mock.rdns'
},

provider: {} as any
};

const mockAnnounceEvent: EIP6963AnnounceProviderEvent = {
type: Eip6963EventName.eip6963announceProvider,
detail: mockProviderDetail
} as any;

// Mock the window object
(global as any).window = {
addEventListener: jest.fn(),
dispatchEvent: jest.fn()
};

// Call the function
requestEIP6963Providers();

// Validate event listener setup and event dispatch
expect((global as any).window.addEventListener)
.toHaveBeenCalledWith(Eip6963EventName.eip6963announceProvider, expect.any(Function));

expect((global as any).window.dispatchEvent).toHaveBeenCalled();

// Simulate the announce event
// Access the mock function calls for addEventListener
const addEventListenerMockCalls = (global as any).window.addEventListener.mock.calls;

// Retrieve the first call to addEventListener and access its second argument
const eventListenerArg = addEventListenerMockCalls[0][1];

// Now "eventListenerArg" represents the function to be called when the event occurs
const announceEventListener = eventListenerArg;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
announceEventListener(mockAnnounceEvent);

// Validate if the provider detail is stored in the eip6963Providers map
expect(eip6963Providers.get('1')).toEqual(mockProviderDetail);
});

it('should throw an error if window object is not available', () => {
// Remove the window object
delete (global as any).window;

// Call the function and expect it to throw an error
expect(() => {
requestEIP6963Providers();
}).toThrow("window object not available, EIP-6963 is intended to be used within a browser");
});
});

1 comment on commit 86447cd

@github-actions
Copy link

Choose a reason for hiding this comment

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

Benchmark

Benchmark suite Current: 86447cd Previous: 6c075db Ratio
processingTx 9699 ops/sec (±3.81%) 9301 ops/sec (±4.81%) 0.96
processingContractDeploy 37320 ops/sec (±8.10%) 39129 ops/sec (±7.62%) 1.05
processingContractMethodSend 20012 ops/sec (±4.58%) 19443 ops/sec (±5.19%) 0.97
processingContractMethodCall 39092 ops/sec (±5.88%) 38971 ops/sec (±6.34%) 1.00
abiEncode 43147 ops/sec (±7.93%) 44252 ops/sec (±6.92%) 1.03
abiDecode 30390 ops/sec (±7.20%) 30419 ops/sec (±8.89%) 1.00
sign 1677 ops/sec (±1.00%) 1656 ops/sec (±4.08%) 0.99
verify 377 ops/sec (±0.50%) 373 ops/sec (±0.78%) 0.99

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.