From 8adf9a33830eee1dd08da8a698bf906d009d716f Mon Sep 17 00:00:00 2001 From: BenRey Date: Mon, 18 Nov 2024 10:31:05 +0100 Subject: [PATCH] feat: enhance MetaMask integration with wallet unlock prompts and connection checks --- package-lock.json | 4 +- package.json | 3 +- src/massaStation/MassaStationWallet.ts | 2 +- src/metamaskSnap/MetamaskWallet.ts | 14 ++++- src/metamaskSnap/metamask.ts | 71 ++++++++++++++++++++++++++ src/metamaskSnap/snap.ts | 46 ++++++++++++++--- src/walletsManager/walletList.ts | 5 +- 7 files changed, 129 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90f278b..4dcfc14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ "bs58check": "^4.0.0", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", - "lodash.isequal": "^4.5.0", - "loglevel": "^1.9.2" + "lodash.isequal": "^4.5.0" }, "devDependencies": { "@babel/preset-env": "^7.22.14", @@ -7571,6 +7570,7 @@ "version": "1.9.2", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" diff --git a/package.json b/package.json index c90b02e..c57da9a 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,7 @@ "bs58check": "^4.0.0", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", - "lodash.isequal": "^4.5.0", - "loglevel": "^1.9.2" + "lodash.isequal": "^4.5.0" }, "devDependencies": { "@babel/preset-env": "^7.22.14", diff --git a/src/massaStation/MassaStationWallet.ts b/src/massaStation/MassaStationWallet.ts index f5e2dac..31de085 100644 --- a/src/massaStation/MassaStationWallet.ts +++ b/src/massaStation/MassaStationWallet.ts @@ -44,7 +44,7 @@ export class MassaStationWallet implements Wallet { } static async createIfInstalled(): Promise { - if (isMassaWalletEnabled()) { + if (await isMassaWalletEnabled()) { return new MassaStationWallet(); } return null; diff --git a/src/metamaskSnap/MetamaskWallet.ts b/src/metamaskSnap/MetamaskWallet.ts index 7bfdbe9..4b5b9d9 100644 --- a/src/metamaskSnap/MetamaskWallet.ts +++ b/src/metamaskSnap/MetamaskWallet.ts @@ -6,7 +6,11 @@ import { Provider, } from '@massalabs/massa-web3'; import { WalletName } from '../wallet'; -import { getMetamaskProvider } from './metamask'; +import { + getMetamaskProvider, + isMetaMaskUnlocked, + promptAndWaitForWalletUnlock, +} from './metamask'; import { connectSnap, getMassaSnapInfo } from './snap'; import { MetamaskAccount } from './MetamaskAccount'; import { MetaMaskInpageProvider } from '@metamask/providers'; @@ -32,6 +36,7 @@ export class MetamaskWallet implements Wallet { static async createIfInstalled(): Promise { try { const metamask = await getMetamaskProvider(); + console.log('metamask', metamask); if (!metamask) return null; return new MetamaskWallet(metamask); @@ -132,7 +137,14 @@ export class MetamaskWallet implements Wallet { public async connect() { try { + const isUnlocked = await isMetaMaskUnlocked(); + + if (!isUnlocked) { + await promptAndWaitForWalletUnlock(); + } + const snap = await getMassaSnapInfo(this.metamaskProvider); + if (!snap) { await connectSnap(this.metamaskProvider); } diff --git a/src/metamaskSnap/metamask.ts b/src/metamaskSnap/metamask.ts index 83e81a1..8935e9d 100644 --- a/src/metamaskSnap/metamask.ts +++ b/src/metamaskSnap/metamask.ts @@ -124,3 +124,74 @@ export async function getMetamaskProvider(): Promise 0; +} + +export function isMetamaskInstalled() { + return Boolean(window.ethereum); +} + +export async function promptAndWaitForWalletUnlock(): Promise { + if (typeof window.ethereum === 'undefined') { + throw new Error( + 'MetaMask is not installed. Please install it and try again.', + ); + } + + const ethereum = window.ethereum; + + try { + // Prompt the user to unlock the wallet + await ethereum.request({ method: 'eth_requestAccounts' }); + + // Wait for accounts to become available + return new Promise((resolve, reject) => { + const checkAccounts = async () => { + try { + const accounts: string[] = await ethereum.request({ + method: 'eth_accounts', + }); + if (accounts && accounts.length > 0) { + resolve(accounts); // Wallet is unlocked + } + } catch (error) { + reject( + new Error('Error checking accounts: ' + (error as Error).message), + ); + } + }; + + // Initial check for accounts + checkAccounts(); + + ethereum.on('accountsChanged', (accounts: string[]) => { + if (accounts.length > 0) { + console.log('Wallet unlocked:', accounts); + resolve(accounts); + } else { + console.warn('Accounts changed, but no accounts are available.'); + } + }); + ethereum.on('disconnect', () => { + reject(new Error('MetaMask disconnected.')); + }); + }); + } catch (error: any) { + if (error.code === 4001) { + throw new Error('User rejected the request.'); + } else if (error.message.includes('User closed popup')) { + throw new Error('MetaMask popup was closed without connecting.'); + } else { + throw new Error('Wallet unlocking failed.'); + } + } +} diff --git a/src/metamaskSnap/snap.ts b/src/metamaskSnap/snap.ts index 0bebfd4..21457dc 100644 --- a/src/metamaskSnap/snap.ts +++ b/src/metamaskSnap/snap.ts @@ -2,14 +2,11 @@ import type { MetaMaskInpageProvider } from '@metamask/providers'; import { MASSA_SNAP_ID } from './config'; import type { GetSnapsResponse, Snap } from './types'; -import log from 'loglevel'; - -log.setLevel('error'); const getInstalledSnaps = async ( provider: MetaMaskInpageProvider, ): Promise => - await provider.request({ + provider.request({ method: 'wallet_getSnaps', }); @@ -25,7 +22,7 @@ export const connectSnap = async ( snapId: string = MASSA_SNAP_ID, params: Record<'version' | string, unknown> = {}, ) => { - await provider.request({ + provider.request({ method: 'wallet_requestSnaps', params: { [snapId]: params, @@ -45,7 +42,7 @@ export const getMassaSnapInfo = async ( snap.id === MASSA_SNAP_ID && (!version || snap.version === version), ); } catch (error) { - log.error('Failed to obtain installed snap', error); + console.error('Failed to obtain installed snap', error); return undefined; } }; @@ -58,3 +55,40 @@ export const showPrivateKey = async (provider: MetaMaskInpageProvider) => { }; export const isLocalSnap = (snapId: string) => snapId.startsWith('local:'); + +export async function isDappConnectedToSnap(snapId: string): Promise { + if (typeof window.ethereum === 'undefined') { + console.error('MetaMask is not installed.'); + return false; + } + + try { + // Request all installed snaps + const installedSnaps = await window.ethereum.request({ + method: 'wallet_getSnaps', + }); + + // Check if the specific Snap is installed + const snap = installedSnaps[snapId]; + if (!snap) { + console.log(`Snap with ID ${snapId} is not installed.`); + return false; + } + + // Check if the current dApp is allowed + const currentOrigin = window.location.origin; + const allowedOrigins = + snap.permissions?.snap_allowedOrigins?.caveats?.[0]?.value || []; + + if (allowedOrigins.includes(currentOrigin)) { + console.log(`DApp ${currentOrigin} is already connected to the Snap.`); + return true; + } else { + console.log(`DApp ${currentOrigin} is NOT connected to the Snap.`); + return false; + } + } catch (error) { + console.error('Error checking Snap connection:', error); + return false; + } +} diff --git a/src/walletsManager/walletList.ts b/src/walletsManager/walletList.ts index 9f002a6..67353b2 100644 --- a/src/walletsManager/walletList.ts +++ b/src/walletsManager/walletList.ts @@ -5,9 +5,6 @@ import { wait } from '../utils/time'; import { MassaStationWallet } from '../massaStation/MassaStationWallet'; import { WalletName } from '../wallet/types'; import { MetamaskWallet } from '../metamaskSnap/MetamaskWallet'; -import log from 'loglevel'; - -log.setLevel('error'); export const supportedWallets: WalletInterfaces = [ BearbyWallet, @@ -22,7 +19,7 @@ export async function getWallets(delay = 200): Promise { try { return await WalletClass.createIfInstalled(); } catch (error) { - log.error(`Error initializing wallet ${WalletClass.name}:`, error); + console.error(`Error initializing wallet ${WalletClass.name}:`, error); } return null; });