diff --git a/package-lock.json b/package-lock.json index 478deddd..5d01a3d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "watchit", - "version": "2.2.0-beta.3", + "version": "2.2.0-beta.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "watchit", - "version": "2.2.0-beta.3", + "version": "2.2.0-beta.7", "dependencies": { "@helia/unixfs": "^4.0.1", "@hookform/resolvers": "^3.1.1", diff --git a/src/App.tsx b/src/App.tsx index 157087d7..11e710f5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,6 +46,7 @@ import { ResponsiveOverlay } from '@src/components/responsive-overlay'; import { Buffer } from 'buffer'; import { Provider } from 'react-redux'; +import { MetaMaskProvider } from '@metamask/sdk-react'; window.Buffer = Buffer; @@ -67,32 +68,52 @@ export default function App() { useScrollToTop(); return ( - - - - - - - - - - - - - - - - - - + { + const isMM = (window as any).ethereum?.isMetaMask; + if (typeof (window as any).ethereum === 'undefined' || !isMM) { + // Mobile version / no extension + window.location.href = 'https://metamask.app.link'; + } else { + // Desktop with MetaMask extension + window.location.href = url; + } + }, + // headless: true, // If we wanted to personalize our own modals + }} + > + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/clients/viem/walletClient.ts b/src/clients/viem/walletClient.ts deleted file mode 100644 index 69cbe0dc..00000000 --- a/src/clients/viem/walletClient.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createWalletClient, custom } from 'viem'; -import { polygonAmoy } from 'viem/chains'; -import 'viem/window'; -import { publicClient } from '@src/clients/viem/publicClient'; - -export async function ConnectWalletClient() { - // window.ethereum is an object provided by MetaMask or other web3 wallets - let transport; - if (window.ethereum) { - // If window.ethereum exists, create a custom transport using it - transport = custom(window.ethereum); - } else { - // If window.ethereum is not available, throw an error - const errorMessage = - 'MetaMask or another web3 wallet is not installed. Please install one to proceed.'; - throw new Error(errorMessage); - } - // Return the wallet client - return createWalletClient({ - chain: polygonAmoy, - transport: transport, - }); -} - -export function ConnectPublicClient() { - return publicClient; -} diff --git a/src/hooks/use-authorize-policy.ts b/src/hooks/use-authorize-policy.ts index 8643d147..23c02936 100644 --- a/src/hooks/use-authorize-policy.ts +++ b/src/hooks/use-authorize-policy.ts @@ -100,6 +100,7 @@ export const useAuthorizePolicy = (): useAuthorizePolicyHook => { setData(receipt); setLoading(false); } catch (err: any) { + console.log('AUTHORIZE: ',err); setError(ERRORS.UNKNOWN_ERROR); setLoading(false); } diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index 664c91a3..5339bdec 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -1,6 +1,5 @@ import { useState } from 'react'; -import { parseUnits } from 'viem'; -import { ConnectWalletClient } from '@src/clients/viem/walletClient'; +import { Address, parseUnits } from 'viem'; import LedgerVaultAbi from '@src/config/abi/LedgerVault.json'; import MMCAbi from '@src/config/abi/MMC.json'; import { GLOBAL_CONSTANTS } from '@src/config-global'; @@ -8,6 +7,8 @@ import { publicClient } from '@src/clients/viem/publicClient'; import { ERRORS } from '@notifications/errors.ts'; import { notifyInfo } from '@notifications/internal-notifications.ts'; import { INFO } from '@notifications/info.ts'; +import { useMetaMask } from '@src/hooks/use-metamask.ts'; +// import { enqueueSnackbar } from 'notistack'; // SAME HERE interface DepositParams { @@ -33,31 +34,29 @@ export const useDepositMetamask = (): UseDepositHook => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const { walletClient, account: address } = useMetaMask(); const deposit = async ({ recipient, amount }: DepositParams) => { + if (!address) return; + setLoading(true); setError(null); try { - // 1) Connect to the wallet (MetaMask) - const walletClient = await ConnectWalletClient(); - - // 2) Retrieve the user's address from the wallet - const [senderAddress] = await walletClient.requestAddresses(); - - // 3) Convert the amount to Wei (18 decimals for MMC) + // 1) Convert the amount to Wei (18 decimals for MMC) const weiAmount = parseUnits(amount.toString(), 18); // Notify the user that we are sending approve transaction to the network notifyInfo(INFO.APPROVE_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); - // 4) First transaction: approve - const approveTxHash = await walletClient.writeContract({ + // 2) First transaction: approve + const approveTxHash = await walletClient?.writeContract({ address: GLOBAL_CONSTANTS.MMC_ADDRESS, abi: MMCAbi.abi, functionName: 'approve', args: [GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, weiAmount], - account: senderAddress, + chain: undefined, + account: address, }); // Notify the user that we are now waiting for the approve transaction to be confirmed @@ -65,19 +64,20 @@ export const useDepositMetamask = (): UseDepositHook => { // Wait for the approve transaction to be mined const approveReceipt = await publicClient.waitForTransactionReceipt({ - hash: approveTxHash, + hash: approveTxHash as Address, }); // Notify the user that we are now sending the deposit transaction notifyInfo(INFO.DEPOSIT_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); - // 5) Second transaction: deposit - const depositTxHash = await walletClient.writeContract({ + // 3) Second transaction: deposit + const depositTxHash = await walletClient?.writeContract({ address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, abi: LedgerVaultAbi.abi, functionName: 'deposit', args: [recipient, weiAmount, GLOBAL_CONSTANTS.MMC_ADDRESS], - account: senderAddress, + chain: undefined, + account: address, }); // Notify the user that we are now waiting for the deposit transaction to be confirmed @@ -85,10 +85,10 @@ export const useDepositMetamask = (): UseDepositHook => { // Wait for the deposit transaction to be mined const depositReceipt = await publicClient.waitForTransactionReceipt({ - hash: depositTxHash, + hash: depositTxHash as Address, }); - // 6) Store data about both transactions + // 4) Store data about both transactions setData({ approveTxHash, depositTxHash, @@ -97,6 +97,9 @@ export const useDepositMetamask = (): UseDepositHook => { }); } catch (err: any) { // If something fails (either approve or deposit), set an error + console.log('DEPOSIT FAILING ERROR: ', err); + // const errorMessage = typeof err === 'object' ? JSON.stringify(err) : String(err); + // enqueueSnackbar(`DEPOSIT FAILING ERROR: ${errorMessage}`); setError(ERRORS.UNKNOWN_ERROR); throw err; } finally { diff --git a/src/hooks/use-deposit.ts b/src/hooks/use-deposit.ts index 27675f8f..4e409788 100644 --- a/src/hooks/use-deposit.ts +++ b/src/hooks/use-deposit.ts @@ -102,7 +102,11 @@ export const useDeposit = (): UseDepositHook => { setData(receipt); setLoading(false); } catch (err: any) { + setError(ERRORS.UNKNOWN_ERROR); + + console.error('USE DEPOSIT:', err); + setLoading(false); } }; diff --git a/src/hooks/use-metamask.ts b/src/hooks/use-metamask.ts index f5f6c247..838e7fb7 100644 --- a/src/hooks/use-metamask.ts +++ b/src/hooks/use-metamask.ts @@ -1,32 +1,108 @@ -import { useEffect, useState } from 'react'; -import { Address } from 'viem'; -import { connectToMetaMask } from '@src/utils/metamask'; -import { notifyError } from '@notifications/internal-notifications.ts'; -import { ERRORS } from '@notifications/errors.ts'; - -export const useMetaMask = () => { - const [address, setAddress] = useState
(); - const [connecting, setConnecting] = useState(false); - - useEffect(() => { - const walletConnected = localStorage.getItem('walletConnected'); - if (walletConnected === 'true') { - connect(); - } - }, []); - - const connect = async () => { - setConnecting(true); - try { - const walletAddress = await connectToMetaMask(); - setAddress(walletAddress); - localStorage.setItem('walletConnected', 'true'); - } catch (err) { - notifyError(ERRORS.METAMASK_CONNECTING_ERROR); - } finally { - setConnecting(false); +// REACT IMPORTS +import { useCallback } from 'react'; + +// VIEM IMPORTS +import { createWalletClient, custom, WalletClient } from 'viem'; +import type { Address } from 'viem'; +import { polygonAmoy } from 'viem/chains'; + +// METAMASK IMPORTS +import { useSDK } from '@metamask/sdk-react'; +// import { enqueueSnackbar } from 'notistack'; + +/** + * Represents the shape of the object returned by the useMetaMask hook. + */ +interface UseMetaMaskReturn { + /** + * Initiates the MetaMask connection flow via the MetaMask SDK. + * Returns a list of connected accounts if successful. + * + * Throws an error if the MetaMask SDK isn't yet initialized. + */ + connect: () => Promise; + + /** + * Indicates whether the user is currently connected to MetaMask. + */ + connected: boolean; + + /** + * The address of the connected account, if any. + */ + account?: Address; + + /** + * The chain ID of the currently connected network. + */ + chainId?: string; + + /** + * A viem WalletClient instance that uses the MetaMask provider as transport. + */ + walletClient?: WalletClient; + + /** + * Indicates if the connection flow is still in progress. + */ + loading: boolean; + + /** + * Contains any error that occurred during connection (if applicable). + */ + error?: Error; +} + +/** + * Custom React hook that leverages the MetaMask React SDK + * along with viem to create a WalletClient for the Polygon Amoy chain. + * + * @returns An object with methods and states for handling the MetaMask connection. + */ +export function useMetaMask(): UseMetaMaskReturn { + const { + sdk, + connected, + chainId: sdkChainId, + account, + connecting, + error, + provider, + } = useSDK(); + + // useEffect(() => { + // if (error) { + // const errorMessage = typeof error === 'object' ? JSON.stringify(error) : String(error); + // enqueueSnackbar(`METAMASK ERROR: ${errorMessage}`); + // } + // }, [error]); + + /** + * We define 'connect' as a guaranteed function (non-optional). + * If the sdk is not ready, this method will throw an error. + */ + const connect = useCallback(async (): Promise => { + if (!sdk) { + throw new Error('MetaMask SDK is not initialized.'); } - }; + return sdk.connect(); + }, [sdk]); - return { address, connecting, connect, setAddress }; -}; + // Generate a viem wallet client using the MetaMask provider, if available. + const walletClient = provider + ? createWalletClient({ + chain: polygonAmoy, + transport: custom(provider), + }) + : undefined; + + return { + connect, + connected, + account: account as Address, + chainId: sdkChainId, + walletClient, + loading: connecting, + error, + }; +} diff --git a/src/hooks/use-transfer.ts b/src/hooks/use-transfer.ts index 3c90e13e..4d813166 100644 --- a/src/hooks/use-transfer.ts +++ b/src/hooks/use-transfer.ts @@ -75,6 +75,7 @@ export const useTransfer = (): UseTransferHook => { setData(receipt); setLoading(false); } catch (err: any) { + console.log(err); setError(ERRORS.UNKNOWN_ERROR); setLoading(false); } diff --git a/src/hooks/use-withdraw-metamask.ts b/src/hooks/use-withdraw-metamask.ts index 41f8789e..3f97100f 100644 --- a/src/hooks/use-withdraw-metamask.ts +++ b/src/hooks/use-withdraw-metamask.ts @@ -1,8 +1,7 @@ import { useState } from 'react'; -import { parseUnits } from 'viem'; +import { Address, parseUnits } from 'viem'; // Project imports -import { ConnectWalletClient } from '@src/clients/viem/walletClient'; import LedgerVaultAbi from '@src/config/abi/LedgerVault.json'; import { GLOBAL_CONSTANTS } from '@src/config-global'; import { publicClient } from '@src/clients/viem/publicClient'; @@ -11,6 +10,7 @@ import { publicClient } from '@src/clients/viem/publicClient'; import { ERRORS } from '@notifications/errors.ts'; import { notifyInfo } from '@notifications/internal-notifications.ts'; import { INFO } from '@notifications/info.ts'; +import { useMetaMask } from '@src/hooks/use-metamask.ts'; interface WithdrawParams { recipient: string; // Address receiving the funds @@ -33,18 +33,15 @@ export const useWithdrawMetamask = (): UseWithdrawHook => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const { walletClient, account: address } = useMetaMask(); const withdraw = async ({ recipient, amount }: WithdrawParams) => { + if (!address) return; + setLoading(true); setError(null); try { - // 1) Connect to the wallet (MetaMask) - const walletClient = await ConnectWalletClient(); - - // 2) Retrieve the user's address from the wallet - const [senderAddress] = await walletClient.requestAddresses(); - // 3) Convert the amount to Wei (18 decimals) const weiAmount = parseUnits(amount.toString(), 18); @@ -54,12 +51,13 @@ export const useWithdrawMetamask = (): UseWithdrawHook => { }); // 4) Send the withdraw transaction - const withdrawTxHash = await walletClient.writeContract({ + const withdrawTxHash = await walletClient?.writeContract({ address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, abi: LedgerVaultAbi.abi, functionName: 'withdraw', args: [recipient, weiAmount, GLOBAL_CONSTANTS.MMC_ADDRESS], - account: senderAddress, + chain: undefined, + account: address as Address, }); // Notify the user that we are waiting for confirmation @@ -69,7 +67,7 @@ export const useWithdrawMetamask = (): UseWithdrawHook => { // Wait for the withdraw transaction to be mined const withdrawReceipt = await publicClient.waitForTransactionReceipt({ - hash: withdrawTxHash, + hash: withdrawTxHash as Address, }); // Store the transaction data diff --git a/src/sections/finance/components/finance-change-wallet.tsx b/src/sections/finance/components/finance-change-wallet.tsx deleted file mode 100644 index 898f6ffe..00000000 --- a/src/sections/finance/components/finance-change-wallet.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// React and libraries imports -import 'viem/window'; -import { FC } from 'react'; -import { Address } from 'viem'; - -// @mui -import Button from '@mui/material/Button'; -import Box from '@mui/material/Box'; - -// Project imports -import TextMaxLine from '@src/components/text-max-line'; -import { useResponsive } from '@src/hooks/use-responsive.ts'; -import { notifyError, notifySuccess } from '@notifications/internal-notifications.ts'; -import { SUCCESS } from '@notifications/success.ts'; -import { ERRORS } from '@notifications/errors.ts'; - -type FinanceChangeWalletProps = { - onChangingWallet?: (address: Address) => void; -}; - -const FinanceChangeWallet: FC = ({ onChangingWallet }) => { - // Function to handle wallet changes (User can interchange wallets using MetaMask extension) - const handleChangeWallet = async () => { - window?.ethereum - ?.request({ - method: 'wallet_requestPermissions', - params: [ - { - eth_accounts: {}, - }, - ], - }) - .then(() => - window?.ethereum?.request({ method: 'eth_requestAccounts' }).then((accounts: string[]) => { - onChangingWallet?.(accounts[0] as Address); - notifySuccess(SUCCESS.WALLET_CHANGED_SUCCESFULLY); - }) - ) - .catch(() => { - notifyError(ERRORS.FAILED_CHANGE_WALLET_ERROR); - }); - }; - - const mdUp = useResponsive('up', 'md'); - - return ( - - - - ); -}; - -export default FinanceChangeWallet; diff --git a/src/sections/finance/components/finance-deposit-from-metamask.tsx b/src/sections/finance/components/finance-deposit-from-metamask.tsx index 85b12c58..5de4a2cf 100644 --- a/src/sections/finance/components/finance-deposit-from-metamask.tsx +++ b/src/sections/finance/components/finance-deposit-from-metamask.tsx @@ -10,6 +10,7 @@ import { useDepositMetamask } from '@src/hooks/use-deposit-metamask'; import FinanceDeposit from '@src/sections/finance/components/finance-deposit'; import FinanceMetamaskLoader from '@src/sections/finance/components/finance-metamask-loader.tsx'; import FinanceMetamaskButton from '@src/sections/finance/components/finance-metamask-button.tsx'; +import FinanceMetamaskHelper from "@src/sections/finance/components/finance-metamask-helper.tsx"; interface FinanceDepositFromMetamaskProps { onClose: () => void; @@ -18,10 +19,10 @@ interface FinanceDepositFromMetamaskProps { const FinanceDepositFromMetamask: FC = ({ onClose }) => { const sessionData = useSelector((state: any) => state.auth.session); const depositHook = useDepositMetamask(); - const { address, connecting, connect } = useMetaMask(); + const { account: address, loading, connect } = useMetaMask(); - if (connecting) return ; - if (!address) return ; + if (loading) return ; + if (!address) return <>; return ; }; diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index 179e94ef..6517daa1 100644 --- a/src/sections/finance/components/finance-deposit.tsx +++ b/src/sections/finance/components/finance-deposit.tsx @@ -63,7 +63,7 @@ interface FinanceDepositProps { * - `onClose` */ const FinanceDeposit: FC = ({ address, recipient, depositHook, onClose }) => { - const [amount, setAmount] = useState(); + const [amount, setAmount] = useState(''); const [helperText, setHelperText] = useState(""); const { balance } = useGetMmcContractBalance(address); const { deposit, loading: depositLoading, error } = depositHook; @@ -88,7 +88,7 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo return; } // Validate amount > 0 and <= balance - if (amount <= 0 || amount > (balance ?? 0)) { + if (Number(amount) <= 0 || Number(amount) > (balance ?? 0)) { notifyWarning(WARNING.INVALID_DEPOSIT_AMOUNT); return; } @@ -96,7 +96,7 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo // TODO refactor this!!!!! try { setLocalLoading(true); - await deposit({ recipient: recipient ?? address, amount }); + await deposit({ recipient: recipient ?? address, amount: Number(amount) }); notifySuccess(SUCCESS.DEPOSIT_SUCCESSFULLY); onClose(); } catch (err) { @@ -156,7 +156,6 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo = ({ address, recipient, depositHo rainbowComponent={RainbowEffect} loading={isBusy} actionLoading={depositLoading} - amount={amount ?? 0} + amount={Number(amount) ?? 0} balance={balance ?? 0} label={'Confirm'} onConfirmAction={handleConfirmDeposit} diff --git a/src/sections/finance/components/finance-metamask-button.tsx b/src/sections/finance/components/finance-metamask-button.tsx index d5e5c223..0baa0bad 100644 --- a/src/sections/finance/components/finance-metamask-button.tsx +++ b/src/sections/finance/components/finance-metamask-button.tsx @@ -9,7 +9,7 @@ import Iconify from '@src/components/iconify'; const FinanceMetamaskButton: FC<{ connect: () => void }> = ({ connect }) => { return + + + ) +} + +export default FinanceMetamaskHelper; diff --git a/src/sections/finance/components/finance-withdraw-from-metamask.tsx b/src/sections/finance/components/finance-withdraw-from-metamask.tsx index 87f14b58..231e8669 100644 --- a/src/sections/finance/components/finance-withdraw-from-metamask.tsx +++ b/src/sections/finance/components/finance-withdraw-from-metamask.tsx @@ -7,6 +7,7 @@ import FinanceWithdraw from '@src/sections/finance/components/finance-withdraw'; import FinanceMetamaskLoader from '@src/sections/finance/components/finance-metamask-loader.tsx'; import FinanceMetamaskButton from '@src/sections/finance/components/finance-metamask-button.tsx'; import { useWithdraw } from '@src/hooks/use-withdraw.ts'; +import FinanceMetamaskHelper from "@src/sections/finance/components/finance-metamask-helper.tsx"; interface FinanceWithdrawFromMetamaskProps { onClose: () => void; @@ -14,10 +15,10 @@ interface FinanceWithdrawFromMetamaskProps { const FinanceWithdrawFromMetamask: FC = ({ onClose }) => { const withdrawHook = useWithdraw(); - const { address, connecting, connect } = useMetaMask(); + const { account: address, loading, connect } = useMetaMask(); - if (connecting) return ; - if (!address) return ; + if (loading) return ; + if (!address) return <>; return ; }; diff --git a/src/sections/finance/components/finance-withdraw.tsx b/src/sections/finance/components/finance-withdraw.tsx index 4ff82887..ecc07c5e 100644 --- a/src/sections/finance/components/finance-withdraw.tsx +++ b/src/sections/finance/components/finance-withdraw.tsx @@ -37,7 +37,7 @@ interface FinanceWithdrawProps { // ---------------------------------------------------------------------- const FinanceWithdraw: FC = ({ address, withdrawHook, onClose }) => { - const [amount, setAmount] = useState(); + const [amount, setAmount] = useState(''); const [amountError, setAmountError] = useState(false); const [amountHelperText, setAmountHelperText] = useState(''); const [localLoading, setLocalLoading] = useState(false); @@ -54,13 +54,13 @@ const FinanceWithdraw: FC = ({ address, withdrawHook, onCl const handleConfirmWithdraw = useCallback(async () => { if (!amount) return; - if (amount <= 0 || amount > (balance ?? 0)) { + if (Number(amount) <= 0 || amount > (balance ?? 0)) { notifyWarning(WARNING.INVALID_WITHDRAW_AMOUNT); return; } try { setLocalLoading(true); - await withdraw({ amount, recipient: address }); + await withdraw({ amount: Number(amount), recipient: address }); notifySuccess(SUCCESS.WITHDRAW_SUCCESSFULLY); onClose(); } catch (err: any) { @@ -118,7 +118,6 @@ const FinanceWithdraw: FC = ({ address, withdrawHook, onCl = ({ address, withdrawHook, onCl rainbowComponent={RainbowEffect} loading={localLoading} actionLoading={withdrawLoading} - amount={amount ?? 0} + amount={Number(amount) ?? 0} balance={balance ?? 0} label={'Confirm'} onConfirmAction={handleConfirmWithdraw} diff --git a/src/utils/metamask.ts b/src/utils/metamask.ts deleted file mode 100644 index 3b3b9aa1..00000000 --- a/src/utils/metamask.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MetaMaskSDK } from '@metamask/sdk'; -import { GLOBAL_CONSTANTS } from '@src/config-global'; -import { Address } from 'viem'; - -/** - * Connects to MetaMask and retrieves the wallet address. - * @returns A promise resolving to the connected wallet address. - */ -export const connectToMetaMask = async (): Promise
=> { - const MMSDK = new MetaMaskSDK({ - infuraAPIKey: GLOBAL_CONSTANTS.INFURA_API_KEY, - dappMetadata: { - name: 'WatchitApp', - url: window.location.href, - }, - openDeeplink: (url) => { - // @ts-ignore - const isMM = window.ethereum?.isMetaMask; - - if (typeof window.ethereum === 'undefined' || !isMM) { - window.location.href = 'https://metamask.app.link'; - } else { - window.location.href = url; - } - }, - }); - - await MMSDK.init(); - const accounts = await MMSDK.connect(); - return accounts[0] as Address; -};