Skip to content

Commit

Permalink
feat: enhance MetaMask integration with wallet unlock prompts and con…
Browse files Browse the repository at this point in the history
…nection checks
  • Loading branch information
Ben-Rey committed Nov 18, 2024
1 parent 0b84e5c commit 8adf9a3
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 16 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/massaStation/MassaStationWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class MassaStationWallet implements Wallet {
}

static async createIfInstalled(): Promise<Wallet | null> {
if (isMassaWalletEnabled()) {
if (await isMassaWalletEnabled()) {
return new MassaStationWallet();
}
return null;
Expand Down
14 changes: 13 additions & 1 deletion src/metamaskSnap/MetamaskWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -32,6 +36,7 @@ export class MetamaskWallet implements Wallet {
static async createIfInstalled(): Promise<Wallet | null> {
try {
const metamask = await getMetamaskProvider();
console.log('metamask', metamask);

Check warning on line 39 in src/metamaskSnap/MetamaskWallet.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected console statement

Check warning on line 39 in src/metamaskSnap/MetamaskWallet.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected console statement
if (!metamask) return null;

return new MetamaskWallet(metamask);
Expand Down Expand Up @@ -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);
}
Expand Down
71 changes: 71 additions & 0 deletions src/metamaskSnap/metamask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,74 @@ export async function getMetamaskProvider(): Promise<MetaMaskInpageProvider | nu

return null;
}

export async function isMetaMaskUnlocked() {
if (!isMetamaskInstalled()) {
return false;
}

const accounts: string[] = await window.ethereum.request({
method: 'eth_accounts',
});
return accounts.length > 0;
}

export function isMetamaskInstalled() {
return Boolean(window.ethereum);
}

export async function promptAndWaitForWalletUnlock(): Promise<string[]> {
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);

Check warning on line 178 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected console statement

Check warning on line 178 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected console statement
resolve(accounts);
} else {
console.warn('Accounts changed, but no accounts are available.');

Check warning on line 181 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected console statement

Check warning on line 181 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected console statement
}
});
ethereum.on('disconnect', () => {
reject(new Error('MetaMask disconnected.'));
});
});
} catch (error: any) {

Check warning on line 188 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected any. Specify a different type

Check warning on line 188 in src/metamaskSnap/metamask.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected any. Specify a different type
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.');
}
}
}
46 changes: 40 additions & 6 deletions src/metamaskSnap/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GetSnapsResponse> =>
await provider.request({
provider.request({
method: 'wallet_getSnaps',
});

Expand All @@ -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,
Expand All @@ -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);

Check warning on line 45 in src/metamaskSnap/snap.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected console statement

Check warning on line 45 in src/metamaskSnap/snap.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected console statement
return undefined;
}
};
Expand All @@ -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<boolean> {
if (typeof window.ethereum === 'undefined') {
console.error('MetaMask is not installed.');

Check warning on line 61 in src/metamaskSnap/snap.ts

View workflow job for this annotation

GitHub Actions / format

Unexpected console statement

Check warning on line 61 in src/metamaskSnap/snap.ts

View workflow job for this annotation

GitHub Actions / format / format

Unexpected console statement
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;
}
}
5 changes: 1 addition & 4 deletions src/walletsManager/walletList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,7 +19,7 @@ export async function getWallets(delay = 200): Promise<Wallet[]> {
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;
});
Expand Down

0 comments on commit 8adf9a3

Please sign in to comment.