Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: metamask sdk #443

Merged
merged 3 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
moduleNameMapper: {
'@utils/cache': '<rootDir>/src/utils/cache',
'@utils/object': '<rootDir>/src/utils/object',
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
},
};
35,365 changes: 24,143 additions & 11,222 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@fairdatasociety/blossom": "^0.3.0",
"@fairdatasociety/fdp-storage": "^0.11.0",
"@headlessui/react": "^1.7.14",
"@metamask/sdk": "^0.5.6",
"@types/react-blockies": "^1.4.1",
"axios": "^0.21.1",
"copy-to-clipboard": "^3.3.3",
Expand All @@ -42,6 +43,7 @@
"react": "^18.2.0",
"react-blockies": "^1.4.1",
"react-confetti": "^6.1.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-dropzone": "^11.5.1",
"react-hook-form": "^7.34.2",
Expand Down
8 changes: 4 additions & 4 deletions src/api/files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FdpStorage, FileItem } from '@fairdatasociety/fdp-storage';
import formatURL from '@utils/formatURL';
import { formatUrl } from '@utils/url';

interface DownloadFileData {
filename: string;
Expand Down Expand Up @@ -32,7 +32,7 @@ export const receiveFile = async (
directory: string
) => {
try {
const writePath = directory === 'root' ? '/' : '/' + formatURL(directory);
const writePath = directory === 'root' ? '/' : '/' + formatUrl(directory);

const shareFileInfoResult = await fdp.file.saveShared(
podName,
Expand All @@ -51,7 +51,7 @@ export async function downloadFile(
data: DownloadFileData
): Promise<Blob> {
const writePath =
data.directory === 'root' ? '/' : '/' + formatURL(data.directory) + '/';
data.directory === 'root' ? '/' : '/' + formatUrl(data.directory) + '/';
const downloadFile = await fdp.file.downloadData(
data.podName,
`${writePath}${data.filename}`
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function uploadFile(
data: UploadFileData
): Promise<FileItem> {
const writePath =
data.directory === 'root' ? '' : '/' + formatURL(data.directory);
data.directory === 'root' ? '' : '/' + formatUrl(data.directory);
const f = await data.file.arrayBuffer();
const fileBytes = new Uint8Array(f);
const fileMetadata = await fdp.file.uploadData(
Expand Down
47 changes: 35 additions & 12 deletions src/components/Connect/Metamask/MetamaskConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import MetamaskIcon from '@media/UI/metamask.svg';
import {
decryptWallet,
getBasicSignatureWallet,
isMetamaskAvailable,
getMetamaskDeeplinkUrl,
} from '@utils/metamask';
import { useRouter } from 'next/router';
import { useFdpStorage } from '@context/FdpStorageContext';
import MetamaskNotFoundModal from '@components/Modals/MetamaskNotFoundModal/MetamaskNotFoundModal';
import { useContext, useState } from 'react';
import { useContext, useEffect, 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';

interface MetamaskConnectProps {
onConnect: () => void;
Expand All @@ -33,6 +35,8 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
} = useFdpStorage();
const { setErrorMessage, setAddress, setMnemonic } = useContext(UserContext);
const router = useRouter();
const { connectMetamask, metamaskProvider, metamaskWalletAddress } =
useMetamask();

/**
* When user retrieved `signature wallet` from metamask - send info that invite was participated
Expand Down Expand Up @@ -77,24 +81,43 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
};

/**
* Clicking my connect to metamask button
* Connect to metamask
*/
const connect = async (): Promise<void> => {
const connectMetamaskHandle = async (): Promise<void> => {
if (isMobile) {
// If a user visits Fairdrive on a mobile device and wants to use Metamask,
// the site will be opened in the Metamask browser
window.open(
getMetamaskDeeplinkUrl(process.env.NEXT_PUBLIC_FAIRDRIVEHOST)
);

return;
}

try {
setLoading(true);
await connectMetamask();
} catch (error) {
console.error(error);
setErrorMessage(String(error.message || error));
setLoading(false);
}
};

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

setLocalBasicWallet(await getBasicSignatureWallet());
setLocalBasicWallet(
await getBasicSignatureWallet(metamaskProvider, metamaskWalletAddress)
);
setShowPasswordModal(true);
} catch (error) {
console.error(error);
setErrorMessage(String(error.message || error));
}
};

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

return (
<>
Expand All @@ -110,7 +133,7 @@ const MetamaskConnect = ({ onConnect }: MetamaskConnectProps) => {
<MetamaskIcon className="inline-block ml-2" />
)
}
onClick={connect}
onClick={connectMetamaskHandle}
/>
<MetamaskNotFoundModal
showModal={showNotFoundModal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import InfoDark from '@media/UI/info-dark.svg';
import ThemeContext from '@context/ThemeContext';
import { RegistrationRequest } from '@fairdatasociety/fdp-storage/dist/account/types';
import { useLocales } from '@context/LocalesContext';
import { useMetamask } from '@context/MetamaskContext';

interface MetamaskCreateAccountProps {
username: string;
Expand Down Expand Up @@ -48,6 +49,7 @@ export default function MetamaskCreateAccount({
const { theme } = useContext(ThemeContext);
const { setUser } = useContext(UserContext);
const { intl } = useLocales();
const { metamaskProvider, metamaskWalletAddress } = useMetamask();
const address = fdpClientRef.current.account.wallet?.address;

const timer = useRef<NodeJS.Timeout | null>();
Expand Down Expand Up @@ -110,8 +112,16 @@ export default function MetamaskCreateAccount({
const send = async () => {
try {
setSending(true);
await switchToNetwork('0x' + network.chainId.toString(16));
await sendAmount(address, utils.formatEther(minBalance));
await switchToNetwork(
metamaskProvider,
'0x' + network.chainId.toString(16)
);
await sendAmount(
metamaskProvider,
metamaskWalletAddress,
address,
utils.formatEther(minBalance)
);
} catch (error) {
console.error(error);
} finally {
Expand Down
16 changes: 13 additions & 3 deletions src/components/Modals/TopUpInviteModal/TopUpInviteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
switchToNetwork,
} from '@utils/metamask';
import { useLocales } from '@context/LocalesContext';
import { useMetamask } from '@context/MetamaskContext';

export interface TopUpInviteModalProps extends CreatorModalProps {
invite: Invite;
Expand All @@ -29,6 +30,7 @@ const TopUpInviteModal: FC<TopUpInviteModalProps> = ({
const [successMessage, setSuccessMessage] = useState('');
const [amount, setAmount] = useState('0.0101');
const { intl } = useLocales();
const { metamaskProvider, metamaskWalletAddress } = useMetamask();

const onMetamaskTopUp = async () => {
setErrorMessage('');
Expand All @@ -40,12 +42,20 @@ const TopUpInviteModal: FC<TopUpInviteModalProps> = ({

setLoading(true);
try {
const chainId = await getChainId();
const chainId = await getChainId(metamaskProvider);
if (Number(chainId) !== NETWORK_SEPOLIA) {
await switchToNetwork('0x' + NETWORK_SEPOLIA.toString(16));
await switchToNetwork(
metamaskProvider,
'0x' + NETWORK_SEPOLIA.toString(16)
);
}

await sendAmount(invite.address, amount);
await sendAmount(
metamaskProvider,
metamaskWalletAddress,
invite.address,
amount
);
// TODO Sepolia shouldn't be hardcoded
setSuccessMessage(intl.get('SEPOLIA_ETH_HAS_BEEN_SENT', { amount }));
} catch (e) {
Expand Down
70 changes: 70 additions & 0 deletions src/context/MetamaskContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { createContext, useContext, useState } from 'react';
import { MetaMaskSDK } from '@metamask/sdk';

/**
* Metamask context props
*/
interface MetamaskContextProps {
connectMetamask: () => Promise<void>;
metamaskWalletAddress: string;
metamaskProvider: any;
}

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

export const MetamaskProvider: React.FC = ({ children }) => {
const [metamaskWalletAddress, setMetamaskWalletAddress] =
useState<string>('');
const [metamaskProvider, setMetamaskProvider] = useState<any>(null);

const connectMetamask = async (name = 'Fairdrive'): Promise<void> => {
if (metamaskProvider && metamaskWalletAddress) {
return;
}

const MMSDK = new MetaMaskSDK({
dappMetadata: {
name,
},
});

let accounts: Partial<unknown>;
if (window?.ethereum?.isMetaMask) {
accounts = await window?.ethereum?.request({
method: 'eth_requestAccounts',
params: [],
});
} else {
accounts = await MMSDK.connect();
}

if (!accounts || !accounts[0]) {
throw new Error('No accounts available');
}

setMetamaskProvider(
window?.ethereum?.isMetaMask ? window?.ethereum : MMSDK.getProvider()
);
setMetamaskWalletAddress(accounts[0]);
};

return (
<MetamaskContext.Provider
value={{
connectMetamask,
metamaskWalletAddress,
metamaskProvider,
}}
>
{children}
</MetamaskContext.Provider>
);
};

export const useMetamask = (): MetamaskContextProps => {
const context = useContext(MetamaskContext);
if (!context) {
throw new Error('useMetamask must be used within a MetamaskProvider');
}
return context;
};
49 changes: 26 additions & 23 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AnimatePresence } from 'framer-motion';
import { DialogProvider } from '@context/DialogsContext';
import Dialogs from '@components/Dialogs/Dialogs';
import { LocalesProvider } from '@context/LocalesContext';
import { MetamaskProvider } from '@context/MetamaskContext';
/* eslint-disable no-console */

/**
Expand Down Expand Up @@ -43,29 +44,31 @@ import { LocalesProvider } from '@context/LocalesContext';

function MyApp({ Component, pageProps, router }: AppProps) {
return (
<LocalesProvider>
<FdpStorageProvider>
<Matomo>
<ThemeProvider>
<UserProvider>
<SearchProvider>
<PodProvider>
<DialogProvider>
<Head>
<title>Fairdrive</title>
</Head>
<Dialogs />
<AnimatePresence mode="wait" initial={false}>
<Component {...pageProps} key={router.asPath} />
</AnimatePresence>
</DialogProvider>
</PodProvider>
</SearchProvider>
</UserProvider>
</ThemeProvider>
</Matomo>
</FdpStorageProvider>
</LocalesProvider>
<MetamaskProvider>
<LocalesProvider>
<FdpStorageProvider>
<Matomo>
<ThemeProvider>
<UserProvider>
<SearchProvider>
<PodProvider>
<DialogProvider>
<Head>
<title>Fairdrive</title>
</Head>
<Dialogs />
<AnimatePresence mode="wait" initial={false}>
<Component {...pageProps} key={router.asPath} />
</AnimatePresence>
</DialogProvider>
</PodProvider>
</SearchProvider>
</UserProvider>
</ThemeProvider>
</Matomo>
</FdpStorageProvider>
</LocalesProvider>
</MetamaskProvider>
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/utils/formatDirectory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import formatURL from '@utils/formatURL';
import { formatUrl } from '@utils/url';

export default function writePath(directory: string): string {
return directory === 'root' ? '/' : '/' + formatURL(directory) + '/';
return directory === 'root' ? '/' : '/' + formatUrl(directory) + '/';
}
3 changes: 0 additions & 3 deletions src/utils/formatURL.ts

This file was deleted.