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

EIP6963 web3 provider update event #6855

Merged
merged 9 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions docs/docs/guides/web3_providers_guide/eip6963.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ sidebar_label: 'EIP-6963: Multi Injected Provider Discovery'

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.
Web3.js library has utility function for discovery of injected providers using `requestEIP6963Providers()` function. When `requestEIP6963Providers()` is called it returns Promise object that resolves to `Map<string, EIP6963ProviderDetail>` object containing list of providers. For updated providers `eip6963:providersMapUpdated` event is emitted and it has updated Map object.

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

```ts
export interface EIP6963ProviderDetail {
Expand Down Expand Up @@ -40,8 +40,13 @@ Following code snippet demonstrates usage of `requestEIP6963Providers()` functio

import { Web3 } from 'web3';

const providers = Web3.requestEIP6963Providers();
window.addEventListener('web3:providersMapUpdated', (event) => {
jdevcs marked this conversation as resolved.
Show resolved Hide resolved
console.log(event.detail); // This will log the populated providers map object
// add logic here for updating UI of your DApp
});

// Call the function and wait for the promise to resolve
let providers = await Web3.requestEIP6963Providers();
for (const [key, value] of providers) {
console.log(value);

Expand Down
1 change: 1 addition & 0 deletions packages/web3/src/providers.exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { Eip1193Provider, SocketProvider } from 'web3-utils';

export * as http from 'web3-providers-http';
export * as ws from 'web3-providers-ws';
export * from './web3_eip6963.js';
30 changes: 22 additions & 8 deletions packages/web3/src/web3_eip6963.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,42 @@ export interface EIP6963RequestProviderEvent extends Event {
type: Eip6963EventName.eip6963requestProvider;
}

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

export const requestEIP6963Providers = () => {
export const web3ProvidersMapUpdated = "web3:providersMapUpdated";
export interface EIP6963ProvidersMapUpdateEvent extends CustomEvent {
type: string;
detail: Map<string, EIP6963ProviderDetail>;
}

if (typeof window === 'undefined')
throw new Error(
"window object not available, EIP-6963 is intended to be used within a browser"
);
export const requestEIP6963Providers = () => {
return new Promise((resolve, reject) => {
if (typeof window === 'undefined') {
reject(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(
eip6963ProvidersMap.set(
event.detail.info.uuid,
event.detail);

const newEvent: EIP6963ProvidersMapUpdateEvent = new CustomEvent(
web3ProvidersMapUpdated,
{ detail: eip6963ProvidersMap }
);

window.dispatchEvent(newEvent);
resolve(eip6963ProvidersMap);

}
);

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

return eip6963Providers;
});
}


103 changes: 41 additions & 62 deletions packages/web3/test/unit/web3eip6963.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,71 +16,50 @@ 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");
});
describe('requestEIP6963Providers', () => {
it('should reject with an error if window object is not available', async () => {
// Mocking window object absence
(global as any).window = undefined;

await expect(requestEIP6963Providers()).rejects.toThrow("window object not available, EIP-6963 is intended to be used within a browser");
});

it('should resolve with updated providers map when events are triggered', async () => {
class CustomEventPolyfill extends Event {
public detail: any;
public constructor(eventType: string, eventInitDict: any) {
super(eventType, eventInitDict);
this.detail = eventInitDict.detail;
}
}

(global as any).CustomEvent = CustomEventPolyfill;

const mockProviderDetail = {
info: { uuid: 'test-uuid', name: 'Test Provider', icon: 'test-icon', rdns: 'test-rdns' },
provider: {} // Mock provider object
};

const mockEvent = {
type: 'eip6963:announceProvider',
detail: mockProviderDetail
};

// Mock window methods
(global as any).window = {
addEventListener: jest.fn().mockImplementation(

(_event, callback) => callback(mockEvent)), // eslint-disable-line
dispatchEvent: jest.fn()
};

const result = await requestEIP6963Providers();

expect(result).toEqual(new Map([['test-uuid', mockProviderDetail]]));
});
});
Loading