11import type {
2+ EIP6963AnnounceProviderEvent ,
3+ EIP6963RequestProviderEvent ,
24 MetaMaskInpageProvider ,
35 RequestArguments ,
46} from '@metamask/providers' ;
57import { logError } from '@metamask/snaps-utils' ;
6- import type { JsonRpcError , JsonRpcParams } from '@metamask/utils' ;
8+ import { assert , type JsonRpcError , type JsonRpcParams } from '@metamask/utils' ;
79import type { BaseQueryFn } from '@reduxjs/toolkit/query/react' ;
810import { createApi } from '@reduxjs/toolkit/query/react' ;
911
1012declare global {
1113 // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
1214 interface Window {
13- ethereum : MetaMaskInpageProvider ;
15+ ethereum : MetaMaskInpageProvider & {
16+ setProvider ?: ( provider : MetaMaskInpageProvider ) => void ;
17+ detected ?: MetaMaskInpageProvider [ ] ;
18+ providers ?: MetaMaskInpageProvider [ ] ;
19+ } ;
20+ }
21+
22+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
23+ interface WindowEventMap {
24+ 'eip6963:requestProvider' : EIP6963RequestProviderEvent ;
25+ 'eip6963:announceProvider' : EIP6963AnnounceProviderEvent ;
1426 }
1527}
1628
@@ -22,6 +34,116 @@ export enum Tag {
2234 UnencryptedTestState = 'Unencrypted Test State' ,
2335}
2436
37+ /**
38+ * Check if the current provider supports snaps by calling `wallet_getSnaps`.
39+ *
40+ * @param provider - The provider to use to check for snaps support. Defaults to
41+ * `window.ethereum`.
42+ * @returns True if the provider supports snaps, false otherwise.
43+ */
44+ async function hasSnapsSupport (
45+ provider : MetaMaskInpageProvider = window . ethereum ,
46+ ) {
47+ try {
48+ await provider . request ( {
49+ method : 'wallet_getSnaps' ,
50+ } ) ;
51+
52+ return true ;
53+ } catch {
54+ return false ;
55+ }
56+ }
57+
58+ /**
59+ * Get a MetaMask provider using EIP6963. This will return the first provider
60+ * reporting as MetaMask. If no provider is found after 500ms, this will
61+ * return null instead.
62+ *
63+ * @returns A MetaMask provider if found, otherwise null.
64+ */
65+ async function getMetaMaskEIP6963Provider ( ) {
66+ return new Promise < MetaMaskInpageProvider | null > ( ( resolve ) => {
67+ // Timeout looking for providers after 500ms
68+ const timeout = setTimeout ( ( ) => {
69+ resolveWithCleanup ( null ) ;
70+ } , 500 ) ;
71+
72+ /**
73+ * Resolve the promise with a MetaMask provider and clean up.
74+ *
75+ * @param provider - A MetaMask provider if found, otherwise null.
76+ */
77+ function resolveWithCleanup ( provider : MetaMaskInpageProvider | null ) {
78+ window . removeEventListener (
79+ 'eip6963:announceProvider' ,
80+ onAnnounceProvider ,
81+ ) ;
82+ clearTimeout ( timeout ) ;
83+ resolve ( provider ) ;
84+ }
85+
86+ /**
87+ * Listener for the EIP6963 announceProvider event.
88+ *
89+ * Resolves the promise if a MetaMask provider is found.
90+ *
91+ * @param event - The EIP6963 announceProvider event.
92+ */
93+ function onAnnounceProvider ( event : EIP6963AnnounceProviderEvent ) {
94+ const { info, provider } = event . detail ;
95+
96+ if ( info . rdns . includes ( 'io.metamask' ) ) {
97+ resolveWithCleanup ( provider ) ;
98+ }
99+ }
100+
101+ window . addEventListener ( 'eip6963:announceProvider' , onAnnounceProvider ) ;
102+
103+ window . dispatchEvent ( new Event ( 'eip6963:requestProvider' ) ) ;
104+ } ) ;
105+ }
106+
107+ /**
108+ * Get a provider that supports snaps. This will loop through all the detected
109+ * providers and return the first one that supports snaps.
110+ *
111+ * @returns The provider, or `null` if no provider supports snaps.
112+ */
113+ async function getSnapsProvider ( ) {
114+ if ( typeof window === 'undefined' ) {
115+ return null ;
116+ }
117+
118+ const eip6963Provider = await getMetaMaskEIP6963Provider ( ) ;
119+
120+ if ( eip6963Provider && ( await hasSnapsSupport ( eip6963Provider ) ) ) {
121+ return eip6963Provider ;
122+ }
123+
124+ if ( await hasSnapsSupport ( ) ) {
125+ return window . ethereum ;
126+ }
127+
128+ if ( window . ethereum ?. detected ) {
129+ for ( const provider of window . ethereum . detected ) {
130+ if ( await hasSnapsSupport ( provider ) ) {
131+ return provider ;
132+ }
133+ }
134+ }
135+
136+ if ( window . ethereum ?. providers ) {
137+ for ( const provider of window . ethereum . providers ) {
138+ if ( await hasSnapsSupport ( provider ) ) {
139+ return provider ;
140+ }
141+ }
142+ }
143+
144+ return null ;
145+ }
146+
25147/**
26148 * Base request function for all API calls.
27149 *
@@ -35,7 +157,11 @@ export const request: BaseQueryFn<RequestArguments> = async ({
35157 params,
36158} ) => {
37159 try {
38- const data = await window . ethereum . request ( { method, params } ) ;
160+ const provider = await getSnapsProvider ( ) ;
161+
162+ assert ( provider , 'No Ethereum provider found.' ) ;
163+
164+ const data = await provider . request ( { method, params } ) ;
39165
40166 return { data } ;
41167 } catch ( error : any ) {
0 commit comments