diff --git a/docs/docs/guides/web3_providers_guide/eip6963.md b/docs/docs/guides/web3_providers_guide/eip6963.md new file mode 100644 index 00000000000..c6d9b676804 --- /dev/null +++ b/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')); + } +} + +``` \ No newline at end of file diff --git a/docs/docs/guides/web3_providers_guide/events_listening.md b/docs/docs/guides/web3_providers_guide/events_listening.md index 4208e4d2777..a8e36a3cd22 100644 --- a/docs/docs/guides/web3_providers_guide/events_listening.md +++ b/docs/docs/guides/web3_providers_guide/events_listening.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 sidebar_label: 'Providers Events Listening' --- diff --git a/docs/docs/guides/web3_providers_guide/http.md b/docs/docs/guides/web3_providers_guide/http.md index c30db83873d..06dbc0d8125 100644 --- a/docs/docs/guides/web3_providers_guide/http.md +++ b/docs/docs/guides/web3_providers_guide/http.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 sidebar_label: 'Tutorial: HTTP Provider' --- diff --git a/docs/docs/guides/web3_providers_guide/injected_provider.md b/docs/docs/guides/web3_providers_guide/injected_provider.md index 1088e576507..e6971629c58 100644 --- a/docs/docs/guides/web3_providers_guide/injected_provider.md +++ b/docs/docs/guides/web3_providers_guide/injected_provider.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 7 sidebar_label: 'Tutorial: Injected provider' --- diff --git a/docs/docs/guides/web3_providers_guide/ipc.md b/docs/docs/guides/web3_providers_guide/ipc.md index fd7ad921cc1..1b31eb44f2b 100644 --- a/docs/docs/guides/web3_providers_guide/ipc.md +++ b/docs/docs/guides/web3_providers_guide/ipc.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 sidebar_label: 'Tutorial: IPC Provider' --- diff --git a/docs/docs/guides/web3_providers_guide/truffle.md b/docs/docs/guides/web3_providers_guide/truffle.md index 0c52cb76902..ba98c3a3335 100644 --- a/docs/docs/guides/web3_providers_guide/truffle.md +++ b/docs/docs/guides/web3_providers_guide/truffle.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 sidebar_label: 'Tutorial: Third Party Provider' --- diff --git a/docs/docs/guides/web3_providers_guide/websocket.md b/docs/docs/guides/web3_providers_guide/websocket.md index 5fd95c4e8cf..96288f4e031 100644 --- a/docs/docs/guides/web3_providers_guide/websocket.md +++ b/docs/docs/guides/web3_providers_guide/websocket.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 sidebar_label: 'Tutorial: WebSocket Provider' --- diff --git a/packages/web3/CHANGELOG.md b/packages/web3/CHANGELOG.md index a9f200cb149..e87edb57ec4 100644 --- a/packages/web3/CHANGELOG.md +++ b/packages/web3/CHANGELOG.md @@ -191,4 +191,8 @@ Documentation: - Dependencies updated ( details are in root changelog ) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Added + +- Added EIP-6963 utility function `requestEIP6963Providers` for multi provider discovery \ No newline at end of file diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index e6d52e0da50..0512110d32d 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -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 { @@ -52,6 +53,7 @@ export class Web3< > extends Web3Context { public static version = Web3PkgInfo.version; public static utils = utils; + public static requestEIP6963Providers = requestEIP6963Providers; public static modules = { Web3Eth, Iban, diff --git a/packages/web3/src/web3_eip6963.ts b/packages/web3/src/web3_eip6963.ts new file mode 100644 index 00000000000..61b1a53e520 --- /dev/null +++ b/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 . +*/ + +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 { + info: EIP6963ProviderInfo; + provider: EIP1193Provider; +} + +export interface EIP6963AnnounceProviderEvent extends CustomEvent { + type: Eip6963EventName.eip6963announceProvider; + detail: EIP6963ProviderDetail; +} + +export interface EIP6963RequestProviderEvent extends Event { + type: Eip6963EventName.eip6963requestProvider; +} + +export const eip6963Providers: Map = 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; +} + + diff --git a/packages/web3/test/unit/constructor.test.ts b/packages/web3/test/unit/constructor.test.ts deleted file mode 100644 index 5d08d50bebc..00000000000 --- a/packages/web3/test/unit/constructor.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 . -*/ - -// import Web3ProviderBase from '../../src/index' -// import {ProviderOptions} from '../../types' - -describe('constructs a PLACEHOLDER instance with expected properties', () => { - // let providerOptions: ProviderOptions - - beforeEach(() => { - // providerOptions = { - // providerUrl: 'http://127.0.0.1:8545' - // } - }); - - it('should construct with expected properties', () => { - // const web3ProviderBase = new Web3ProviderBase(providerOptions) - // expect(web3ProviderBase).toMatchObject({ - // _providerUrl: providerOptions.providerUrl - // }) - expect(true).toBeTruthy(); - }); -}); diff --git a/packages/web3/test/unit/web3eip6963.test.ts b/packages/web3/test/unit/web3eip6963.test.ts new file mode 100644 index 00000000000..b584c91e934 --- /dev/null +++ b/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 . +*/ + +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"); + }); +}); \ No newline at end of file