Skip to content

Commit

Permalink
Apply suggestions from code review
Browse files Browse the repository at this point in the history
Co-authored-by: khanti42 <[email protected]>
  • Loading branch information
joaniefromtheblock and khanti42 authored Sep 24, 2024
1 parent 333cef5 commit 5786849
Showing 1 changed file with 173 additions and 24 deletions.
197 changes: 173 additions & 24 deletions wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
}

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<boolean>} - A promise that resolves to `true` if the provider supports Snaps, and `false` otherwise.
*/
const isSupportSnap = async (provider: any): Promise<boolean> => {
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<MetaMaskProvider | null>} - 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<MetaMaskProvider | null> {
let handled = false;
return new Promise<MetaMaskProvider | null>((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<MetaMaskProvider | null>} - 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<MetaMaskProvider | null> {
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<MetaMaskProvider | null>} - A promise that resolves to a MetaMaskProvider object if MetaMask is detected, or `null` otherwise.
*/
async function detectMetamaskSupport(windowObject: Window & typeof globalThis): Promise<MetaMaskProvider | null> {
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.
Expand Down

0 comments on commit 5786849

Please sign in to comment.