From a02f67297faac8f52e8b33f37505f715e34dd3a1 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Thu, 9 Jan 2025 13:11:56 -0600 Subject: [PATCH 01/14] chore: remove unused FinanceChangeWallet component This component is no longer in use and has been fully removed. Key changes include: - Deleted `src/sections/finance/components/finance-change-wallet.tsx`. The removal helps in cleaning up obsolete code and improving maintainability. --- .../components/finance-change-wallet.tsx | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 src/sections/finance/components/finance-change-wallet.tsx 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; From a06830f107b22258cf7b8e4b7b9c688735178ff9 Mon Sep 17 00:00:00 2001 From: jadapema Date: Thu, 9 Jan 2025 16:33:05 -0600 Subject: [PATCH 02/14] refactor: connect wallet client mm --- src/clients/viem/walletClient.ts | 53 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/clients/viem/walletClient.ts b/src/clients/viem/walletClient.ts index 69cbe0dc..a9f7502b 100644 --- a/src/clients/viem/walletClient.ts +++ b/src/clients/viem/walletClient.ts @@ -1,27 +1,44 @@ import { createWalletClient, custom } from 'viem'; import { polygonAmoy } from 'viem/chains'; -import 'viem/window'; -import { publicClient } from '@src/clients/viem/publicClient'; +import { MetaMaskSDK } from '@metamask/sdk'; +import { GLOBAL_CONSTANTS } from '@src/config-global'; 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); + // 1) Create the MetaMaskSDK instance + const MMSDK = new MetaMaskSDK({ + infuraAPIKey: GLOBAL_CONSTANTS.INFURA_API_KEY, + dappMetadata: { + name: 'watchit', + url: window.location.href, + }, + openDeeplink: (url) => { + // redirect to the MetaMask mobile app link + const isMM = (window as any).ethereum?.isMetaMask; + if (typeof (window as any).ethereum === 'undefined' || !isMM) { + // Mobile / no extension + window.location.href = 'https://metamask.app.link'; + } else { + // Desktop with MetaMask extension + window.location.href = url; + } + }, + }); + + // 2) Initialize the SDK if needed + await MMSDK.init(); + + // 3) Get the provider from the SDK + const provider = MMSDK.getProvider(); + if (!provider) { + throw new Error('Could not retrieve the MetaMask SDK provider'); } - // Return the wallet client + + // 4) Create a transport for viem using the provider + const transport = custom(provider); + + // 5) Return a walletClient from viem that uses that provider return createWalletClient({ chain: polygonAmoy, - transport: transport, + transport, }); } - -export function ConnectPublicClient() { - return publicClient; -} From f71b2c7bac512edf06394b93bb2e39fe94c45126 Mon Sep 17 00:00:00 2001 From: jadapema Date: Thu, 9 Jan 2025 16:47:16 -0600 Subject: [PATCH 03/14] refactor: connect wallet client mm --- src/hooks/use-deposit-metamask.ts | 2 ++ src/sections/finance/components/finance-deposit.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index 664c91a3..dfa955f7 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -8,6 +8,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 { enqueueSnackbar } from 'notistack'; // SAME HERE interface DepositParams { @@ -97,6 +98,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); } catch (err: any) { // If something fails (either approve or deposit), set an error + enqueueSnackbar(`deposit err 1: ${JSON.stringify(err)}`); setError(ERRORS.UNKNOWN_ERROR); throw err; } finally { diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index 179e94ef..5d3f25f9 100644 --- a/src/sections/finance/components/finance-deposit.tsx +++ b/src/sections/finance/components/finance-deposit.tsx @@ -24,6 +24,7 @@ import { WARNING } from '@notifications/warnings'; import { SUCCESS } from '@notifications/success'; import { ERRORS } from '@notifications/errors.ts'; import TextField from '@mui/material/TextField'; +import { enqueueSnackbar } from 'notistack'; interface FinanceDepositProps { /** @@ -100,6 +101,7 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo notifySuccess(SUCCESS.DEPOSIT_SUCCESSFULLY); onClose(); } catch (err) { + enqueueSnackbar(`deposit err 2: ${JSON.stringify(err)}`); notifyWarning(WARNING.NO_WALLET_AUTHORIZATION); } finally { setLocalLoading(false); From 43eb4357ccbce415f6fd705d14fccaf721854a0c Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Thu, 9 Jan 2025 21:39:00 -0600 Subject: [PATCH 04/14] refactor(walletClient): remove infuraAPIKey from MMSDK config - Updated `src/clients/viem/walletClient.ts` to remove the use of `infuraAPIKey` from the MetaMaskSDK configuration. - This change simplifies the wallet client setup by eliminating unnecessary dependency on `GLOBAL_CONSTANTS`. - Ensures a cleaner and more maintainable code structure for wallet integration. --- src/clients/viem/walletClient.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/clients/viem/walletClient.ts b/src/clients/viem/walletClient.ts index a9f7502b..bd8bb70b 100644 --- a/src/clients/viem/walletClient.ts +++ b/src/clients/viem/walletClient.ts @@ -1,12 +1,10 @@ import { createWalletClient, custom } from 'viem'; import { polygonAmoy } from 'viem/chains'; import { MetaMaskSDK } from '@metamask/sdk'; -import { GLOBAL_CONSTANTS } from '@src/config-global'; export async function ConnectWalletClient() { // 1) Create the MetaMaskSDK instance const MMSDK = new MetaMaskSDK({ - infuraAPIKey: GLOBAL_CONSTANTS.INFURA_API_KEY, dappMetadata: { name: 'watchit', url: window.location.href, From 19ff2a8e8b12fd122f741b98488c64891102d8f4 Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Thu, 9 Jan 2025 21:44:16 -0600 Subject: [PATCH 05/14] refactor: remove unnecessary props and imports in finance forms - **finance-withdraw.tsx**: Removed `autoFocus` prop from the "Amount to withdraw" `TextField`. - **finance-deposit.tsx**: - Removed unused `enqueueSnackbar` import. - Removed `enqueueSnackbar` call for error logging. - Removed `autoFocus` prop from the "Amount to deposit" `TextField`. These changes simplify the components by removing redundant props and unused imports, improving code clarity and maintainability. --- src/sections/finance/components/finance-deposit.tsx | 3 --- src/sections/finance/components/finance-withdraw.tsx | 1 - 2 files changed, 4 deletions(-) diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index 5d3f25f9..f510d044 100644 --- a/src/sections/finance/components/finance-deposit.tsx +++ b/src/sections/finance/components/finance-deposit.tsx @@ -24,7 +24,6 @@ import { WARNING } from '@notifications/warnings'; import { SUCCESS } from '@notifications/success'; import { ERRORS } from '@notifications/errors.ts'; import TextField from '@mui/material/TextField'; -import { enqueueSnackbar } from 'notistack'; interface FinanceDepositProps { /** @@ -101,7 +100,6 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo notifySuccess(SUCCESS.DEPOSIT_SUCCESSFULLY); onClose(); } catch (err) { - enqueueSnackbar(`deposit err 2: ${JSON.stringify(err)}`); notifyWarning(WARNING.NO_WALLET_AUTHORIZATION); } finally { setLocalLoading(false); @@ -158,7 +156,6 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo = ({ address, withdrawHook, onCl Date: Thu, 9 Jan 2025 21:44:25 -0600 Subject: [PATCH 06/14] refactor: improve error logging and MetaMask connection logic - src/hooks/use-deposit.ts: Added detailed error logs using `console.error`. - src/hooks/use-transfer.ts, src/hooks/use-authorize-policy.ts: Introduced `console.log` for easier error debugging. - src/hooks/use-deposit-metamask.ts: Replaced `enqueueSnackbar` with `console.log` for error messages, removing extra dependency. - src/utils/metamask.ts: Removed unused `GLOBAL_CONSTANTS`, refined MetaMask redirection logic, and ensured better compatibility across platforms. --- src/hooks/use-authorize-policy.ts | 1 + src/hooks/use-deposit-metamask.ts | 3 +-- src/hooks/use-deposit.ts | 4 ++++ src/hooks/use-transfer.ts | 1 + src/utils/metamask.ts | 13 ++++++------- 5 files changed, 13 insertions(+), 9 deletions(-) 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 dfa955f7..1cd3f791 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -8,7 +8,6 @@ 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 { enqueueSnackbar } from 'notistack'; // SAME HERE interface DepositParams { @@ -98,7 +97,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); } catch (err: any) { // If something fails (either approve or deposit), set an error - enqueueSnackbar(`deposit err 1: ${JSON.stringify(err)}`); + console.log('DEPOSIT FAILING ERROR: ', err); 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-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/utils/metamask.ts b/src/utils/metamask.ts index 3b3b9aa1..d57e1c36 100644 --- a/src/utils/metamask.ts +++ b/src/utils/metamask.ts @@ -1,5 +1,4 @@ import { MetaMaskSDK } from '@metamask/sdk'; -import { GLOBAL_CONSTANTS } from '@src/config-global'; import { Address } from 'viem'; /** @@ -8,18 +7,18 @@ import { Address } from 'viem'; */ export const connectToMetaMask = async (): Promise
=> { const MMSDK = new MetaMaskSDK({ - infuraAPIKey: GLOBAL_CONSTANTS.INFURA_API_KEY, dappMetadata: { - name: 'WatchitApp', + name: 'watchit', url: window.location.href, }, openDeeplink: (url) => { - // @ts-ignore - const isMM = window.ethereum?.isMetaMask; - - if (typeof window.ethereum === 'undefined' || !isMM) { + // redirect to the MetaMask mobile app link + const isMM = (window as any).ethereum?.isMetaMask; + if (typeof (window as any).ethereum === 'undefined' || !isMM) { + // Mobile / no extension window.location.href = 'https://metamask.app.link'; } else { + // Desktop with MetaMask extension window.location.href = url; } }, From a1b4b720c8069f498d12f1d291fbae62c4c7c2ee Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 09:56:15 -0600 Subject: [PATCH 07/14] refactor: metamask connection --- package-lock.json | 4 +- src/App.tsx | 75 ++++++---- src/clients/viem/walletClient.ts | 42 ------ src/hooks/use-deposit-metamask.ts | 35 +++-- src/hooks/use-metamask.ts | 129 ++++++++++++++---- src/hooks/use-withdraw-metamask.ts | 20 ++- .../finance-deposit-from-metamask.tsx | 4 +- .../finance-withdraw-from-metamask.tsx | 4 +- src/utils/metamask.ts | 30 ---- 9 files changed, 179 insertions(+), 164 deletions(-) delete mode 100644 src/clients/viem/walletClient.ts delete mode 100644 src/utils/metamask.ts 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 bd8bb70b..00000000 --- a/src/clients/viem/walletClient.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createWalletClient, custom } from 'viem'; -import { polygonAmoy } from 'viem/chains'; -import { MetaMaskSDK } from '@metamask/sdk'; - -export async function ConnectWalletClient() { - // 1) Create the MetaMaskSDK instance - const MMSDK = new MetaMaskSDK({ - dappMetadata: { - name: 'watchit', - url: window.location.href, - }, - openDeeplink: (url) => { - // redirect to the MetaMask mobile app link - const isMM = (window as any).ethereum?.isMetaMask; - if (typeof (window as any).ethereum === 'undefined' || !isMM) { - // Mobile / no extension - window.location.href = 'https://metamask.app.link'; - } else { - // Desktop with MetaMask extension - window.location.href = url; - } - }, - }); - - // 2) Initialize the SDK if needed - await MMSDK.init(); - - // 3) Get the provider from the SDK - const provider = MMSDK.getProvider(); - if (!provider) { - throw new Error('Could not retrieve the MetaMask SDK provider'); - } - - // 4) Create a transport for viem using the provider - const transport = custom(provider); - - // 5) Return a walletClient from viem that uses that provider - return createWalletClient({ - chain: polygonAmoy, - transport, - }); -} diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index 1cd3f791..e70c2b0c 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,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'; // SAME HERE interface DepositParams { @@ -33,31 +33,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 +63,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 +84,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, diff --git a/src/hooks/use-metamask.ts b/src/hooks/use-metamask.ts index f5f6c247..71f21ddb 100644 --- a/src/hooks/use-metamask.ts +++ b/src/hooks/use-metamask.ts @@ -1,32 +1,101 @@ -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'; + +/** + * 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 { + // Destructure relevant data from the useSDK hook. + const { + sdk, + connected, + chainId: sdkChainId, + account, + connecting, + error, + provider, + } = useSDK(); + + /** + * 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]); + + // Generate a viem wallet client using the MetaMask provider, if available. + const walletClient = provider + ? createWalletClient({ + chain: polygonAmoy, + transport: custom(provider), + }) + : undefined; - return { address, connecting, connect, setAddress }; -}; + return { + connect, + connected, + account: account as Address, + chainId: sdkChainId, + walletClient, + loading: connecting, + error, + }; +} 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-deposit-from-metamask.tsx b/src/sections/finance/components/finance-deposit-from-metamask.tsx index 85b12c58..3a43d6ef 100644 --- a/src/sections/finance/components/finance-deposit-from-metamask.tsx +++ b/src/sections/finance/components/finance-deposit-from-metamask.tsx @@ -18,9 +18,9 @@ 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 (loading) return ; if (!address) return ; return ; diff --git a/src/sections/finance/components/finance-withdraw-from-metamask.tsx b/src/sections/finance/components/finance-withdraw-from-metamask.tsx index 87f14b58..eb4c6162 100644 --- a/src/sections/finance/components/finance-withdraw-from-metamask.tsx +++ b/src/sections/finance/components/finance-withdraw-from-metamask.tsx @@ -14,9 +14,9 @@ interface FinanceWithdrawFromMetamaskProps { const FinanceWithdrawFromMetamask: FC = ({ onClose }) => { const withdrawHook = useWithdraw(); - const { address, connecting, connect } = useMetaMask(); + const { account: address, loading, connect } = useMetaMask(); - if (connecting) return ; + if (loading) return ; if (!address) return ; return ; diff --git a/src/utils/metamask.ts b/src/utils/metamask.ts deleted file mode 100644 index d57e1c36..00000000 --- a/src/utils/metamask.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { MetaMaskSDK } from '@metamask/sdk'; -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({ - dappMetadata: { - name: 'watchit', - url: window.location.href, - }, - openDeeplink: (url) => { - // redirect to the MetaMask mobile app link - const isMM = (window as any).ethereum?.isMetaMask; - if (typeof (window as any).ethereum === 'undefined' || !isMM) { - // Mobile / no extension - window.location.href = 'https://metamask.app.link'; - } else { - // Desktop with MetaMask extension - window.location.href = url; - } - }, - }); - - await MMSDK.init(); - const accounts = await MMSDK.connect(); - return accounts[0] as Address; -}; From 86e84be1feede66394967fe16dc794624bac7b5c Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Fri, 10 Jan 2025 09:57:57 -0600 Subject: [PATCH 08/14] fix: ensure consistent handling of numeric input for finance forms - **finance-withdraw.tsx**: - Updated `amount` state to accept `number | string` to fix type mismatch issues. - Ensured `amount` is converted to `Number` before validations, operations, and API calls. - Adjusted `amount` value used in the display to handle proper type casting. - **finance-deposit.tsx**: - Similar changes as withdraw: updated `amount` state to `number | string`. - Converted `amount` to `Number` for validations, operations, and API calls. - Refactored type handling for better consistency across inputs. --- src/sections/finance/components/finance-deposit.tsx | 8 ++++---- src/sections/finance/components/finance-withdraw.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index f510d044..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) { @@ -170,7 +170,7 @@ const FinanceDeposit: FC = ({ 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-withdraw.tsx b/src/sections/finance/components/finance-withdraw.tsx index a25cc4db..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) { @@ -132,7 +132,7 @@ const FinanceWithdraw: FC = ({ address, withdrawHook, onCl rainbowComponent={RainbowEffect} loading={localLoading} actionLoading={withdrawLoading} - amount={amount ?? 0} + amount={Number(amount) ?? 0} balance={balance ?? 0} label={'Confirm'} onConfirmAction={handleConfirmWithdraw} From e93708997f2c8267bbea54127ad052b41943cf78 Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 10:05:23 -0600 Subject: [PATCH 09/14] debug: added debug notifications --- src/hooks/use-deposit-metamask.ts | 16 +++++++++------- .../finance/components/finance-deposit.tsx | 7 ++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index e70c2b0c..a386dc62 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -5,9 +5,10 @@ import MMCAbi from '@src/config/abi/MMC.json'; import { GLOBAL_CONSTANTS } from '@src/config-global'; 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 { 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 { @@ -46,7 +47,7 @@ export const useDepositMetamask = (): UseDepositHook => { 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 } }); + // notifyInfo(INFO.APPROVE_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); // 2) First transaction: approve const approveTxHash = await walletClient?.writeContract({ @@ -59,7 +60,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now waiting for the approve transaction to be confirmed - notifyInfo(INFO.APPROVE_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); + // notifyInfo(INFO.APPROVE_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); // Wait for the approve transaction to be mined const approveReceipt = await publicClient.waitForTransactionReceipt({ @@ -67,7 +68,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now sending the deposit transaction - notifyInfo(INFO.DEPOSIT_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); + // notifyInfo(INFO.DEPOSIT_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); // 3) Second transaction: deposit const depositTxHash = await walletClient?.writeContract({ @@ -80,7 +81,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now waiting for the deposit transaction to be confirmed - notifyInfo(INFO.DEPOSIT_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); + // notifyInfo(INFO.DEPOSIT_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); // Wait for the deposit transaction to be mined const depositReceipt = await publicClient.waitForTransactionReceipt({ @@ -97,7 +98,8 @@ export const useDepositMetamask = (): UseDepositHook => { } catch (err: any) { // If something fails (either approve or deposit), set an error console.log('DEPOSIT FAILING ERROR: ', err); - setError(ERRORS.UNKNOWN_ERROR); + enqueueSnackbar(`DEPOSIT FAILING ERROR: ${JSON.stringify(err)}`); + // setError(ERRORS.UNKNOWN_ERROR); throw err; } finally { // Reset loading state diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index 6517daa1..5216b3c6 100644 --- a/src/sections/finance/components/finance-deposit.tsx +++ b/src/sections/finance/components/finance-deposit.tsx @@ -19,10 +19,11 @@ import { UseDepositHook } from '@src/hooks/use-deposit'; import { truncateAddress } from '@src/utils/wallet'; // NOTIFICATIONS IMPORTS -import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications'; +// import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications'; +import { notifySuccess, notifyWarning } from '@notifications/internal-notifications'; import { WARNING } from '@notifications/warnings'; import { SUCCESS } from '@notifications/success'; -import { ERRORS } from '@notifications/errors.ts'; +// import { ERRORS } from '@notifications/errors.ts'; import TextField from '@mui/material/TextField'; interface FinanceDepositProps { @@ -74,7 +75,7 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo useEffect(() => { if (error) { - notifyError(error as ERRORS); + // notifyError(error as ERRORS); } }, [error]); From 247f005068ae34a5d7ff66ef5c56c0767f2b9891 Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 10:37:48 -0600 Subject: [PATCH 10/14] feat: added detect wallet environment hook --- src/hooks/use-detect-wallet-environment.ts | 43 ++++++++++++++++++++++ src/hooks/use-metamask.ts | 35 +++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/hooks/use-detect-wallet-environment.ts diff --git a/src/hooks/use-detect-wallet-environment.ts b/src/hooks/use-detect-wallet-environment.ts new file mode 100644 index 00000000..19e90a3c --- /dev/null +++ b/src/hooks/use-detect-wallet-environment.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react'; + +export interface WalletEnvironment { + /** Indicates if the user is on a mobile device (based on user agent). */ + isMobile: boolean; + /** Indicates if MetaMask is installed (`window.ethereum.isMetaMask === true`). */ + isMetaMaskInstalled: boolean; + /** Indicates if user is likely in MetaMask’s in-app browser (mobile + isMetaMaskInstalled). */ + isMetaMaskInAppBrowser: boolean; +} + +export function useDetectWalletEnvironment(): WalletEnvironment { + const [environment, setEnvironment] = useState({ + isMobile: false, + isMetaMaskInstalled: false, + isMetaMaskInAppBrowser: false, + }); + + useEffect(() => { + // Make sure we're on a client (browser) environment + if (typeof window === 'undefined') return; + + // 1) Check user agent for mobile + const userAgent = navigator.userAgent || navigator.vendor || ''; + const isMobile = /android|mobile|iphone|ipad|ipod|blackberry|opera mini|iemobile/i.test(userAgent); + + // 2) Check if MetaMask is installed + const ethereum = (window as any).ethereum; + const isMetaMaskInstalled = Boolean(ethereum && ethereum.isMetaMask); + + // 3) Determine if we’re in MetaMask in-app browser on mobile + // (some folks also do userAgent.includes('MetaMaskMobile') as an extra check). + const isMetaMaskInAppBrowser = isMobile && isMetaMaskInstalled; + + setEnvironment({ + isMobile, + isMetaMaskInstalled, + isMetaMaskInAppBrowser, + }); + }, []); + + return environment; +} diff --git a/src/hooks/use-metamask.ts b/src/hooks/use-metamask.ts index 71f21ddb..658582b1 100644 --- a/src/hooks/use-metamask.ts +++ b/src/hooks/use-metamask.ts @@ -1,5 +1,5 @@ // REACT IMPORTS -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; // VIEM IMPORTS import { createWalletClient, custom, WalletClient } from 'viem'; @@ -8,6 +8,8 @@ import { polygonAmoy } from 'viem/chains'; // METAMASK IMPORTS import { useSDK } from '@metamask/sdk-react'; +import { enqueueSnackbar } from 'notistack'; +import { useDetectWalletEnvironment, WalletEnvironment } from '@src/hooks/use-detect-wallet-environment.ts'; /** * Represents the shape of the object returned by the useMetaMask hook. @@ -41,6 +43,16 @@ interface UseMetaMaskReturn { */ walletClient?: WalletClient; + /** + * The detected environment (mobile, isMetaMaskInstalled, in-app browser). + */ + environment: WalletEnvironment; + + /** + * Deeplink or redirect the user to install/open MetaMask if missing. + * */ + deeplinkToMetaMask: () => void; + /** * Indicates if the connection flow is still in progress. */ @@ -59,7 +71,7 @@ interface UseMetaMaskReturn { * @returns An object with methods and states for handling the MetaMask connection. */ export function useMetaMask(): UseMetaMaskReturn { - // Destructure relevant data from the useSDK hook. + const environment = useDetectWalletEnvironment(); const { sdk, connected, @@ -70,6 +82,12 @@ export function useMetaMask(): UseMetaMaskReturn { provider, } = useSDK(); + useEffect(() => { + if (error) { + enqueueSnackbar(`METAMASK ERROR: ${JSON.stringify(error)}`); + } + }, [error]); + /** * We define 'connect' as a guaranteed function (non-optional). * If the sdk is not ready, this method will throw an error. @@ -89,12 +107,25 @@ export function useMetaMask(): UseMetaMaskReturn { }) : undefined; + const deeplinkToMetaMask = useCallback(() => { + // If on mobile but not in MetaMask in-app + if (environment.isMobile && !environment.isMetaMaskInAppBrowser) { + // Typically this link either opens or installs the MetaMask app + window.location.href = 'https://metamask.app.link'; + } else { + // If on desktop with no extension installed + window.open('https://metamask.io/download/', '_blank'); + } + }, [environment.isMobile, environment.isMetaMaskInAppBrowser]); + return { connect, connected, account: account as Address, chainId: sdkChainId, walletClient, + environment, + deeplinkToMetaMask, loading: connecting, error, }; From aca7dc635937c248f5fc774d06530e9471aea57f Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 11:01:17 -0600 Subject: [PATCH 11/14] feat: added detect wallet environment hook --- src/hooks/use-deposit-metamask.ts | 12 ++--- src/hooks/use-detect-wallet-environment.ts | 54 +++++++++++++++---- .../finance/components/finance-deposit.tsx | 7 ++- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index a386dc62..9cfe3311 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -5,8 +5,8 @@ import MMCAbi from '@src/config/abi/MMC.json'; import { GLOBAL_CONSTANTS } from '@src/config-global'; 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 { notifyInfo } from '@notifications/internal-notifications.ts'; +import { INFO } from '@notifications/info.ts'; import { useMetaMask } from '@src/hooks/use-metamask.ts'; import { enqueueSnackbar } from 'notistack'; @@ -47,7 +47,7 @@ export const useDepositMetamask = (): UseDepositHook => { 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 } }); + notifyInfo(INFO.APPROVE_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); // 2) First transaction: approve const approveTxHash = await walletClient?.writeContract({ @@ -60,7 +60,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now waiting for the approve transaction to be confirmed - // notifyInfo(INFO.APPROVE_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); + notifyInfo(INFO.APPROVE_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); // Wait for the approve transaction to be mined const approveReceipt = await publicClient.waitForTransactionReceipt({ @@ -68,7 +68,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now sending the deposit transaction - // notifyInfo(INFO.DEPOSIT_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); + notifyInfo(INFO.DEPOSIT_SENDING_CONFIRMATION, { options: { autoHideDuration: 3000 } }); // 3) Second transaction: deposit const depositTxHash = await walletClient?.writeContract({ @@ -81,7 +81,7 @@ export const useDepositMetamask = (): UseDepositHook => { }); // Notify the user that we are now waiting for the deposit transaction to be confirmed - // notifyInfo(INFO.DEPOSIT_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); + notifyInfo(INFO.DEPOSIT_WAITING_CONFIRMATION, { options: { autoHideDuration: 7000 } }); // Wait for the deposit transaction to be mined const depositReceipt = await publicClient.waitForTransactionReceipt({ diff --git a/src/hooks/use-detect-wallet-environment.ts b/src/hooks/use-detect-wallet-environment.ts index 19e90a3c..019eb718 100644 --- a/src/hooks/use-detect-wallet-environment.ts +++ b/src/hooks/use-detect-wallet-environment.ts @@ -17,21 +17,57 @@ export function useDetectWalletEnvironment(): WalletEnvironment { }); useEffect(() => { - // Make sure we're on a client (browser) environment + // Early return for SSR (no window object) if (typeof window === 'undefined') return; - // 1) Check user agent for mobile + // 1) Detect mobile device const userAgent = navigator.userAgent || navigator.vendor || ''; - const isMobile = /android|mobile|iphone|ipad|ipod|blackberry|opera mini|iemobile/i.test(userAgent); + const lowerUA = userAgent.toLowerCase(); + const isMobile = /android|mobile|iphone|ipad|ipod|blackberry|opera mini|iemobile/i.test(lowerUA); - // 2) Check if MetaMask is installed - const ethereum = (window as any).ethereum; - const isMetaMaskInstalled = Boolean(ethereum && ethereum.isMetaMask); + // 2) Access "ethereum" and analyze it + const { ethereum } = window as any; + let isMetaMaskInstalled = false; - // 3) Determine if we’re in MetaMask in-app browser on mobile - // (some folks also do userAgent.includes('MetaMaskMobile') as an extra check). - const isMetaMaskInAppBrowser = isMobile && isMetaMaskInstalled; + // - Case A: a single provider (not an array of providers) + if (ethereum && !ethereum.providers) { + // Check the classic "isMetaMask" flag + if (ethereum.isMetaMask) { + isMetaMaskInstalled = true; + } else { + // Check for internal "marks" (heuristics) + // Some older versions might expose ethereum._metamask + // or ethereum.providerMap?.MetaMask + if (ethereum._metamask?.isUnlocked !== undefined) { + // Not 100% official, but sometimes indicates MetaMask + isMetaMaskInstalled = true; + } + if (ethereum.providerMap?.MetaMask) { + isMetaMaskInstalled = true; + } + } + } + // - Case B: multiple providers + if (ethereum && Array.isArray(ethereum.providers)) { + const metaMaskProvider = ethereum.providers.find((prov: any) => prov.isMetaMask); + if (metaMaskProvider) { + isMetaMaskInstalled = true; + } + } + + // 3) Determine if it is MetaMask in-app browser (only applies if isMobile + isMetaMaskInstalled) + // We can heuristically check if the userAgent includes specific strings + // from the mobile MetaMask app, such as "MetaMaskMobile" or "metamask". + let isMetaMaskInAppBrowser = false; + if (isMobile && isMetaMaskInstalled) { + // Simple heuristic + if (lowerUA.includes('metamask') || lowerUA.includes('metamaskmobile')) { + isMetaMaskInAppBrowser = true; + } + } + + // 4) Update final state setEnvironment({ isMobile, isMetaMaskInstalled, diff --git a/src/sections/finance/components/finance-deposit.tsx b/src/sections/finance/components/finance-deposit.tsx index 5216b3c6..6517daa1 100644 --- a/src/sections/finance/components/finance-deposit.tsx +++ b/src/sections/finance/components/finance-deposit.tsx @@ -19,11 +19,10 @@ import { UseDepositHook } from '@src/hooks/use-deposit'; import { truncateAddress } from '@src/utils/wallet'; // NOTIFICATIONS IMPORTS -// import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications'; -import { notifySuccess, notifyWarning } from '@notifications/internal-notifications'; +import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications'; import { WARNING } from '@notifications/warnings'; import { SUCCESS } from '@notifications/success'; -// import { ERRORS } from '@notifications/errors.ts'; +import { ERRORS } from '@notifications/errors.ts'; import TextField from '@mui/material/TextField'; interface FinanceDepositProps { @@ -75,7 +74,7 @@ const FinanceDeposit: FC = ({ address, recipient, depositHo useEffect(() => { if (error) { - // notifyError(error as ERRORS); + notifyError(error as ERRORS); } }, [error]); From cff1edc1f27007494787d878d8ee78207e1c75b3 Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 11:13:47 -0600 Subject: [PATCH 12/14] fix: remove wallet environment detection --- src/hooks/use-deposit-metamask.ts | 3 +- src/hooks/use-detect-wallet-environment.ts | 79 ---------------------- src/hooks/use-metamask.ts | 28 +------- 3 files changed, 4 insertions(+), 106 deletions(-) delete mode 100644 src/hooks/use-detect-wallet-environment.ts diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index 9cfe3311..51e16e89 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -98,7 +98,8 @@ export const useDepositMetamask = (): UseDepositHook => { } catch (err: any) { // If something fails (either approve or deposit), set an error console.log('DEPOSIT FAILING ERROR: ', err); - enqueueSnackbar(`DEPOSIT FAILING ERROR: ${JSON.stringify(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-detect-wallet-environment.ts b/src/hooks/use-detect-wallet-environment.ts deleted file mode 100644 index 019eb718..00000000 --- a/src/hooks/use-detect-wallet-environment.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useState } from 'react'; - -export interface WalletEnvironment { - /** Indicates if the user is on a mobile device (based on user agent). */ - isMobile: boolean; - /** Indicates if MetaMask is installed (`window.ethereum.isMetaMask === true`). */ - isMetaMaskInstalled: boolean; - /** Indicates if user is likely in MetaMask’s in-app browser (mobile + isMetaMaskInstalled). */ - isMetaMaskInAppBrowser: boolean; -} - -export function useDetectWalletEnvironment(): WalletEnvironment { - const [environment, setEnvironment] = useState({ - isMobile: false, - isMetaMaskInstalled: false, - isMetaMaskInAppBrowser: false, - }); - - useEffect(() => { - // Early return for SSR (no window object) - if (typeof window === 'undefined') return; - - // 1) Detect mobile device - const userAgent = navigator.userAgent || navigator.vendor || ''; - const lowerUA = userAgent.toLowerCase(); - const isMobile = /android|mobile|iphone|ipad|ipod|blackberry|opera mini|iemobile/i.test(lowerUA); - - // 2) Access "ethereum" and analyze it - const { ethereum } = window as any; - let isMetaMaskInstalled = false; - - // - Case A: a single provider (not an array of providers) - if (ethereum && !ethereum.providers) { - // Check the classic "isMetaMask" flag - if (ethereum.isMetaMask) { - isMetaMaskInstalled = true; - } else { - // Check for internal "marks" (heuristics) - // Some older versions might expose ethereum._metamask - // or ethereum.providerMap?.MetaMask - if (ethereum._metamask?.isUnlocked !== undefined) { - // Not 100% official, but sometimes indicates MetaMask - isMetaMaskInstalled = true; - } - if (ethereum.providerMap?.MetaMask) { - isMetaMaskInstalled = true; - } - } - } - - // - Case B: multiple providers - if (ethereum && Array.isArray(ethereum.providers)) { - const metaMaskProvider = ethereum.providers.find((prov: any) => prov.isMetaMask); - if (metaMaskProvider) { - isMetaMaskInstalled = true; - } - } - - // 3) Determine if it is MetaMask in-app browser (only applies if isMobile + isMetaMaskInstalled) - // We can heuristically check if the userAgent includes specific strings - // from the mobile MetaMask app, such as "MetaMaskMobile" or "metamask". - let isMetaMaskInAppBrowser = false; - if (isMobile && isMetaMaskInstalled) { - // Simple heuristic - if (lowerUA.includes('metamask') || lowerUA.includes('metamaskmobile')) { - isMetaMaskInAppBrowser = true; - } - } - - // 4) Update final state - setEnvironment({ - isMobile, - isMetaMaskInstalled, - isMetaMaskInAppBrowser, - }); - }, []); - - return environment; -} diff --git a/src/hooks/use-metamask.ts b/src/hooks/use-metamask.ts index 658582b1..fbff348a 100644 --- a/src/hooks/use-metamask.ts +++ b/src/hooks/use-metamask.ts @@ -9,7 +9,6 @@ import { polygonAmoy } from 'viem/chains'; // METAMASK IMPORTS import { useSDK } from '@metamask/sdk-react'; import { enqueueSnackbar } from 'notistack'; -import { useDetectWalletEnvironment, WalletEnvironment } from '@src/hooks/use-detect-wallet-environment.ts'; /** * Represents the shape of the object returned by the useMetaMask hook. @@ -43,16 +42,6 @@ interface UseMetaMaskReturn { */ walletClient?: WalletClient; - /** - * The detected environment (mobile, isMetaMaskInstalled, in-app browser). - */ - environment: WalletEnvironment; - - /** - * Deeplink or redirect the user to install/open MetaMask if missing. - * */ - deeplinkToMetaMask: () => void; - /** * Indicates if the connection flow is still in progress. */ @@ -71,7 +60,6 @@ interface UseMetaMaskReturn { * @returns An object with methods and states for handling the MetaMask connection. */ export function useMetaMask(): UseMetaMaskReturn { - const environment = useDetectWalletEnvironment(); const { sdk, connected, @@ -84,7 +72,8 @@ export function useMetaMask(): UseMetaMaskReturn { useEffect(() => { if (error) { - enqueueSnackbar(`METAMASK ERROR: ${JSON.stringify(error)}`); + const errorMessage = typeof error === 'object' ? JSON.stringify(error) : String(error); + enqueueSnackbar(`METAMASK ERROR: ${errorMessage}`); } }, [error]); @@ -107,25 +96,12 @@ export function useMetaMask(): UseMetaMaskReturn { }) : undefined; - const deeplinkToMetaMask = useCallback(() => { - // If on mobile but not in MetaMask in-app - if (environment.isMobile && !environment.isMetaMaskInAppBrowser) { - // Typically this link either opens or installs the MetaMask app - window.location.href = 'https://metamask.app.link'; - } else { - // If on desktop with no extension installed - window.open('https://metamask.io/download/', '_blank'); - } - }, [environment.isMobile, environment.isMetaMaskInAppBrowser]); - return { connect, connected, account: account as Address, chainId: sdkChainId, walletClient, - environment, - deeplinkToMetaMask, loading: connecting, error, }; From d9686a9105a32389a71673b63356c3060213185a Mon Sep 17 00:00:00 2001 From: jadapema Date: Fri, 10 Jan 2025 11:33:04 -0600 Subject: [PATCH 13/14] fix: remove some extra error notifications --- src/hooks/use-deposit-metamask.ts | 8 ++++---- src/hooks/use-metamask.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/hooks/use-deposit-metamask.ts b/src/hooks/use-deposit-metamask.ts index 51e16e89..5339bdec 100644 --- a/src/hooks/use-deposit-metamask.ts +++ b/src/hooks/use-deposit-metamask.ts @@ -8,7 +8,7 @@ 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'; +// import { enqueueSnackbar } from 'notistack'; // SAME HERE interface DepositParams { @@ -98,9 +98,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); + // const errorMessage = typeof err === 'object' ? JSON.stringify(err) : String(err); + // enqueueSnackbar(`DEPOSIT FAILING ERROR: ${errorMessage}`); + setError(ERRORS.UNKNOWN_ERROR); throw err; } finally { // Reset loading state diff --git a/src/hooks/use-metamask.ts b/src/hooks/use-metamask.ts index fbff348a..838e7fb7 100644 --- a/src/hooks/use-metamask.ts +++ b/src/hooks/use-metamask.ts @@ -1,5 +1,5 @@ // REACT IMPORTS -import { useCallback, useEffect } from 'react'; +import { useCallback } from 'react'; // VIEM IMPORTS import { createWalletClient, custom, WalletClient } from 'viem'; @@ -8,7 +8,7 @@ import { polygonAmoy } from 'viem/chains'; // METAMASK IMPORTS import { useSDK } from '@metamask/sdk-react'; -import { enqueueSnackbar } from 'notistack'; +// import { enqueueSnackbar } from 'notistack'; /** * Represents the shape of the object returned by the useMetaMask hook. @@ -70,12 +70,12 @@ export function useMetaMask(): UseMetaMaskReturn { provider, } = useSDK(); - useEffect(() => { - if (error) { - const errorMessage = typeof error === 'object' ? JSON.stringify(error) : String(error); - enqueueSnackbar(`METAMASK ERROR: ${errorMessage}`); - } - }, [error]); + // 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). From e42ec9f3dbb7c389869d0143eb095930da4b259f Mon Sep 17 00:00:00 2001 From: Carlos Andres Perez Ubeda Date: Fri, 10 Jan 2025 12:03:47 -0600 Subject: [PATCH 14/14] feat(finance): add helper component for MetaMask guidance - Introduced `FinanceMetamaskHelper` to guide users without MetaMask. - Updated `finance-withdraw-from-metamask.tsx` to render helper alongside the button. - Updated `finance-deposit-from-metamask.tsx` similarly to include the helper. - Adjusted margin styling in `FinanceMetamaskButton` for consistency. --- .../finance-deposit-from-metamask.tsx | 3 +- .../components/finance-metamask-button.tsx | 2 +- .../components/finance-metamask-helper.tsx | 39 +++++++++++++++++++ .../finance-withdraw-from-metamask.tsx | 3 +- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/sections/finance/components/finance-metamask-helper.tsx diff --git a/src/sections/finance/components/finance-deposit-from-metamask.tsx b/src/sections/finance/components/finance-deposit-from-metamask.tsx index 3a43d6ef..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; @@ -21,7 +22,7 @@ const FinanceDepositFromMetamask: FC = ({ onClo const { account: address, loading, connect } = useMetaMask(); if (loading) return ; - if (!address) return ; + if (!address) return <>; return ; }; 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 eb4c6162..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; @@ -17,7 +18,7 @@ const FinanceWithdrawFromMetamask: FC = ({ onC const { account: address, loading, connect } = useMetaMask(); if (loading) return ; - if (!address) return ; + if (!address) return <>; return ; };