diff --git a/wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md b/wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md index d39715a4fae..f112a78ba61 100644 --- a/wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md +++ b/wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md @@ -10,34 +10,183 @@ Despite this, we do need to explain that the user can reject the prompt to add t Also, in terms of working with Starknet specifically, we may need to explain that a user will need to take some steps to set up a Starknet account before they can actually use it with the dapp, so the dapp should thoughtfully design that onboarding flow. Whether the user needs to add the Snap (and thus they will be completely new to Starknet) or they already have it but their account is not funded or deployed, the dapp should handle those scenarios.--> -## 1. Connection issues +When using get-starknet MetaMask detection, connection and starknet snap installation is handled automatically. -### Wallet connection fails or doesn't respond. +In case you are using `invokeSnaps` directly then this needs to be handled manually. -Ensure that MetaMask is installed and the StarkNet Snap is properly set up. You can check if the Snap is available using: +You need to check first that MetaMask is installed : -```javascript -const availableWallets = getAvailableWallets(); -if (!availableWallets.some(wallet => wallet.type === 'snap')) { - alert('Please install StarkNet Snap in MetaMask to proceed.'); -} -``` - -### The `connect()` function returns null or undefined. - -Verify that the Snap is installed and MetaMask has granted permission for the dapp to connect. Always include error handling in the connect method to manage issues: - -```javascript -try { - const res = await connect(); - if (!res) { - console.log('No wallet connected'); - } -} catch (error) { - console.error('Error connecting to wallet:', error); -} -``` + ```typescript + interface MetaMaskProvider { + isMetaMask: boolean; + request(options: { method: string }): Promise; + } + + declare global { + interface Window { + ethereum?: MetaMaskProvider; + } + } + + /** + * Detects if MetaMask is installed and supports Snaps by invoking the 'wallet_getSnaps' method. + * + * @param {any} provider - The provider object, typically obtained from MetaMask. + * @returns {Promise} - A promise that resolves to `true` if the provider supports Snaps, and `false` otherwise. + */ + const isSupportSnap = async (provider: any): Promise => { + try { + await provider.request({ + method: 'wallet_getSnaps', + }); + return true; + } catch { + return false; + } + }; + + /** + * Type guard function to check if a given object is a valid MetaMaskProvider. + * + * @param {unknown} obj - The object to check. + * @returns {boolean} - `true` if the object is a MetaMask provider, `false` otherwise. + */ + function isMetaMaskProvider(obj: unknown): obj is MetaMaskProvider { + return ( + obj !== null && + typeof obj === 'object' && + obj.hasOwnProperty('isMetaMask') && + obj.hasOwnProperty('request') + ); + } + + /** + * Detects a MetaMask provider by listening for the 'eip6963:announceProvider' event. + * + * @param {Window & typeof globalThis} windowObject - The window object used to access global browser features. + * @param {Object} options - Optional parameters, including a timeout for the detection process. + * @param {number} [options.timeout=3000] - The time to wait (in milliseconds) for the provider to announce itself. + * @returns {Promise} - A promise that resolves to a MetaMaskProvider object if detected, or `null` if no provider is found. + */ + function detectMetaMaskProvider( + windowObject: Window & typeof globalThis, + { timeout = 3000 } = {}, + ): Promise { + let handled = false; + return new Promise((resolve) => { + const handleEIP6963Provider = (event: CustomEvent) => { + const { info, provider } = event.detail; + if ( + ['io.metamask', 'io.metamask.flask'].includes(info.rdns) && + isMetaMaskProvider(provider) + ) { + resolve(provider); + handled = true; + } + }; + + if (typeof windowObject.addEventListener === 'function') { + windowObject.addEventListener( + 'eip6963:announceProvider', + (event: Event) => { + handleEIP6963Provider(event as CustomEvent); + }, + ); + } + + setTimeout(() => { + if (!handled) { + resolve(null); + } + }, timeout); + + if (typeof windowObject.dispatchEvent === 'function') { + windowObject.dispatchEvent(new Event('eip6963:requestProvider')); + } + }); + } + + /** + * Waits for a MetaMask provider to be detected, retrying if necessary. + * + * @param {Window & typeof globalThis} windowObject - The window object used to access global browser features. + * @param {Object} options - Optional parameters, including timeout and retries. + * @param {number} [options.timeout=1000] - The time (in milliseconds) to wait for each detection attempt. + * @param {number} [options.retries=0] - The number of retry attempts if no provider is detected. + * @returns {Promise} - A promise that resolves to a MetaMaskProvider object if detected, or `null` if no provider is found. + */ + async function waitForMetaMaskProvider( + windowObject: Window & typeof globalThis, + { timeout = 1000, retries = 0 } = {}, + ): Promise { + return detectMetaMaskProvider(windowObject, { timeout }) + .catch(function () { + return null; + }) + .then(function (provider) { + if (provider || retries === 0) { + return provider; + } + return waitForMetaMaskProvider(windowObject, { + timeout, + retries: retries - 1, + }); + }); + } + + /** + * Detects if MetaMask is installed by calling the waitForMetaMaskProvider function with retries. + * + * @param {Window & typeof globalThis} windowObject - The window object used to access global browser features. + * @returns {Promise} - A promise that resolves to a MetaMaskProvider object if MetaMask is detected, or `null` otherwise. + */ + async function detectMetamaskSupport(windowObject: Window & typeof globalThis): Promise { + const provider = await waitForMetaMaskProvider(windowObject, { retries: 3 }); + return provider; + } + + ``` + + +This can be used as follow to check for Metamask Snap Support. This will check for MetaMask presence, and the support of Snap in the installed MetaMask version if any. If there is no MetaMask installed or the version of MetaMask does not support snap + + ```typescript + let isMetaMaskInstallRequired = false; + let provider = null; + try { + provider = await detectMetamaskSupport(window); + // Use the new detection method + + if (provider && (await isSupportSnap(provider))) { + isMetaMaskInstallRequired = provider === null; + } else { + isMetaMaskInstallRequired = true; + } + } catch (err) { + isMetaMaskInstallRequired = true; + } + ``` + +In case MetaMask is not installed (e.g. `isMetaMaskInstallRequired=true` we can prompt the user to install MetaMask. + +In case MetaMask is installed, we can prompt the user to install the Snap + + ```typescript + provider + .request({ + method: 'wallet_requestSnaps', + params: { + [snapId]: { version: snapVersion }, + }, + }) + .then(() => { + // The snap has been installed proceed accordingly. + }) + .catch(() => { + // The snap has not been installed (user rejected the installation) + }); + ``` ## 2. Snap permissions ### Snap is not properly approved.