Skip to content

Commit

Permalink
Merge pull request #576 from fairDataSociety/feat/metamask-auto-login
Browse files Browse the repository at this point in the history
feat: metamask auto login
  • Loading branch information
tomicvladan authored Mar 27, 2024
2 parents 7d6aa76 + 03df584 commit e724f17
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 56 deletions.
23 changes: 20 additions & 3 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"axios": "^0.21.1",
"copy-to-clipboard": "^3.3.3",
"cross-blob": "^3.0.1",
"crypto-js": "^4.2.0",
"ethers": "^5.6.9",
"file-saver": "^2.0.5",
"framer-motion": "^10.12.18",
Expand Down Expand Up @@ -59,6 +60,7 @@
"@tailwindcss/aspect-ratio": "^0.2.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/crypto-js": "^4.2.1",
"@types/file-saver": "^2.0.5",
"@types/jest": "^29.5.1",
"@types/node": "^17.0.9",
Expand Down
62 changes: 33 additions & 29 deletions src/components/Connect/Metamask/MetamaskConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import { Button } from '@components/Buttons';
import MetamaskIcon from '@media/UI/metamask.svg';
import {
decryptWallet,
encryptMnemonic,
getBasicSignature,
getBasicSignatureWallet,
getMetamaskDeeplinkUrl,
isMetamaskAvailable,
} from '@utils/metamask';
import { useRouter } from 'next/router';
import { getDefaultNetwork, useFdpStorage } from '@context/FdpStorageContext';
import MetamaskNotFoundModal from '@components/Modals/MetamaskNotFoundModal/MetamaskNotFoundModal';
import { useContext, useEffect, useState } from 'react';
import { useContext, useState } from 'react';
import UserContext from '@context/UserContext';
import Spinner from '@components/Spinner/Spinner';
import { getInvite, login } from '@utils/invite';
import PasswordModal from '@components/Modals/PasswordModal/PasswordModal';
import { isMobile } from 'react-device-detect';
import { useMetamask } from '@context/MetamaskContext';
import { Network } from '@data/networks';
import { setMetamaskMnemonic } from '@utils/localStorage';

interface MetamaskConnectProps {
onConnect: () => void;
Expand All @@ -37,8 +40,12 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
} = useFdpStorage();
const { setErrorMessage, setAddress, setMnemonic } = useContext(UserContext);
const router = useRouter();
const { connectMetamask, metamaskProvider, metamaskWalletAddress } =
useMetamask();
const {
loading: metamaskLoading,
connectMetamask,
metamaskProvider,
metamaskWalletAddress,
} = useMetamask();
const [network] = useState<Network>(getDefaultNetwork());

/**
Expand All @@ -60,7 +67,10 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
*
* @param password Password from modal input
*/
const handlePassword = async (password: string): Promise<void> => {
const handlePassword = async (
password: string,
saveMnemonic: boolean
): Promise<void> => {
try {
const wallet = await decryptWallet(localBasicWallet, password);
const mnemonic = wallet.mnemonic.phrase;
Expand All @@ -74,6 +84,17 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
setAddress(wallet.address);
setMnemonic(mnemonic);

if (saveMnemonic) {
const signature = await getBasicSignature(
metamaskProvider,
metamaskWalletAddress
);

setMetamaskMnemonic(encryptMnemonic(mnemonic, signature));
} else {
setMetamaskMnemonic('');
}

router.push('/drive');
} catch (error) {
console.error(error);
Expand All @@ -99,45 +120,28 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {

try {
setLoading(true);
await connectMetamask();
const { provider, account } = await connectMetamask();

setLocalBasicWallet(await getBasicSignatureWallet(provider, account));

setShowPasswordModal(true);
} catch (error) {
console.error(error);
setErrorMessage(String(error.message || error));
} finally {
setLoading(false);
}
};

useEffect(() => {
async function run() {
if (!(metamaskProvider && metamaskWalletAddress)) {
return;
}

try {
setLocalBasicWallet(
await getBasicSignatureWallet(metamaskProvider, metamaskWalletAddress)
);

setShowPasswordModal(true);
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}

run();
}, [metamaskProvider, metamaskWalletAddress]);

return (
<>
<Button
variant="tertiary-outlined"
className="w-28 h-10 relative text-color-accents-purple-black dark:text-color-accents-grey-lavendar"
label="Metamask"
disabled={loading}
disabled={loading || metamaskLoading}
icon={
loading ? (
loading || metamaskLoading ? (
<Spinner className="absolute top-3 left-6" />
) : (
<MetamaskIcon className="inline-block ml-2" />
Expand Down
16 changes: 14 additions & 2 deletions src/components/Modals/PasswordModal/PasswordModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
getMetamaskPassphraseExplanation,
setMetamaskPassphraseExplanation,
} from '@utils/localStorage';
import CustomCheckbox from '@components/Inputs/CustomCheckbox/CustomCheckbox';

interface PasswordModalProps {
showModal: boolean;
closeModal: () => void;
handleSubmitForm: (password: string) => Promise<void>;
handleSubmitForm: (password: string, save: boolean) => Promise<void>;
}

const PasswordModal: FC<PasswordModalProps> = ({
Expand All @@ -30,12 +31,13 @@ const PasswordModal: FC<PasswordModalProps> = ({
const { errors } = formState;
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [saveMnemonic, setSaveMnemonic] = useState(true);
const { intl } = useLocales();

const handleSubmitButton = async (data) => {
setLoading(true);
try {
await handleSubmitForm(data.password);
await handleSubmitForm(data.password, saveMnemonic);
setMetamaskPassphraseExplanation(false);
closeModal();
} catch (e) {
Expand Down Expand Up @@ -73,6 +75,16 @@ const PasswordModal: FC<PasswordModalProps> = ({

<p className="text-sm mb-5">{intl.get('PASSPHRASE_EXPLANATION')}</p>

<div className="flex">
<CustomCheckbox
className="m-auto mb-8"
name="save"
checked={saveMnemonic}
onChange={() => setSaveMnemonic(!saveMnemonic)}
>
{intl.get('REMEMBER_ME')}
</CustomCheckbox>
</div>
<div className="text-center">
<Button
type="submit"
Expand Down
71 changes: 64 additions & 7 deletions src/context/MetamaskContext.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
import React, { createContext, useContext, useState } from 'react';
import { MetaMaskSDK } from '@metamask/sdk';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { MetaMaskSDK, SDKProvider } from '@metamask/sdk';
import { getMetamaskMnemonic } from '@utils/localStorage';
import { getDefaultNetwork, useFdpStorage } from './FdpStorageContext';
import UserContext from './UserContext';
import { decryptMnemonic, getBasicSignature } from '@utils/metamask';
import { Wallet } from 'ethers';
import { useRouter } from 'next/router';

/**
* Metamask context props
*/
interface MetamaskContextProps {
connectMetamask: () => Promise<void>;
connectMetamask: () => Promise<{ provider: SDKProvider; account: string }>;
reset: () => void;
metamaskWalletAddress: string;
metamaskProvider: any;
loading: boolean;
}

const MetamaskContext = createContext<MetamaskContextProps | null>(null);

export const MetamaskProvider: React.FC = ({ children }) => {
const {
fdpClientRef,
setIsLoggedIn,
setWallet,
setFdpStorageType,
setLoginType,
} = useFdpStorage();
const { setAddress, setMnemonic } = useContext(UserContext);
const [metamaskWalletAddress, setMetamaskWalletAddress] =
useState<string>('');
const [metamaskProvider, setMetamaskProvider] = useState<any>(null);
const [loading, setLoading] = useState(false);
const router = useRouter();

const connectMetamask = async (name = 'Fairdrive'): Promise<void> => {
const connectMetamask = async (
name = 'Fairdrive'
): Promise<{ provider: SDKProvider; account: string }> => {
setMetamaskProvider(null);
setMetamaskWalletAddress('');

Expand All @@ -44,24 +63,62 @@ export const MetamaskProvider: React.FC = ({ children }) => {
throw new Error('No accounts available');
}

setMetamaskProvider(
window?.ethereum?.isMetaMask ? window?.ethereum : MMSDK.getProvider()
);
const provider = window?.ethereum?.isMetaMask
? window?.ethereum
: MMSDK.getProvider();

setMetamaskProvider(provider);
setMetamaskWalletAddress(accounts[0]);

return { provider, account: accounts[0] };
};

const reset = () => {
setMetamaskWalletAddress('');
setMetamaskProvider(null);
};

const autoLogin = async () => {
try {
setLoading(true);
const { provider, account } = await connectMetamask();
const network = getDefaultNetwork();
const signature = await getBasicSignature(provider, account);
const mnemonic = decryptMnemonic(getMetamaskMnemonic(), signature);

const wallet = Wallet.fromMnemonic(mnemonic);

setFdpStorageType('native', network.ensConfig, network.datahubConfig);
fdpClientRef.current.account.setAccountFromMnemonic(mnemonic);
setIsLoggedIn(true);
setLoginType('metamask');
setWallet(wallet);
setAddress(wallet.address);
setMnemonic(mnemonic);

router.push('/drive');
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};

useEffect(() => {
console.log('MetamaskContext');
if (getMetamaskMnemonic()) {
autoLogin();
}
}, []);

return (
<MetamaskContext.Provider
value={{
connectMetamask,
reset,
metamaskWalletAddress,
metamaskProvider,
loading,
}}
>
{children}
Expand Down
Loading

0 comments on commit e724f17

Please sign in to comment.