From f0a2ca8aa58efb1ebf72cb8e1c2b6db8f8bb1a57 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 7 Dec 2023 02:40:16 +0400 Subject: [PATCH] fix (dis)connection errors --- .../frontend/src/api/ethRequests.ts | 25 +-- .../frontend/src/api/gateway.ts | 14 +- .../modules/common/connect-wallet.tsx | 1 - .../components/modules/home/disconnected.tsx | 5 +- .../components/providers/wallet-provider.tsx | 141 ++++++++++++----- .../frontend/src/lib/constants.ts | 2 +- .../walletextension/frontend/src/lib/utils.ts | 7 +- .../frontend/src/routes/index.ts | 2 +- .../frontend/src/services/ethService.ts | 148 ++++++++++-------- .../src/services/useGatewayService.ts | 13 +- .../src/types/interfaces/WalletInterfaces.ts | 6 +- 11 files changed, 214 insertions(+), 150 deletions(-) diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts index a975518ea1..ef2556d8e5 100644 --- a/tools/walletextension/frontend/src/api/ethRequests.ts +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -77,7 +77,7 @@ export const getSignature = async (account: string, data: any) => { }); }; -export const getUserID = async (provider: ethers.providers.Web3Provider) => { +export const getToken = async (provider: ethers.providers.Web3Provider) => { if (!provider) { showToast( ToastType.DESTRUCTIVE, @@ -88,12 +88,12 @@ export const getUserID = async (provider: ethers.providers.Web3Provider) => { try { if (await isTenChain()) { - const id = await provider.send(requestMethods.getStorageAt, [ + const token = await provider.send(requestMethods.getStorageAt, [ userStorageAddress, getRandomIntAsString(0, 1000), null, ]); - return id; + return token; } else { return null; } @@ -128,16 +128,18 @@ export async function addNetworkToMetaMask(rpcUrls: string[]) { } export async function authenticateAccountWithTenGatewayEIP712( - userID: string, + token: string, account: string ): Promise { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to revoke accounts"); - return; + if (!token) { + return showToast( + ToastType.INFO, + "Encryption token not found. Please try again later." + ); } try { - const isAuthenticated = await accountIsAuthenticated(userID, account); + const isAuthenticated = await accountIsAuthenticated(token, account); if (isAuthenticated.status) { return { status: true, @@ -148,17 +150,18 @@ export async function authenticateAccountWithTenGatewayEIP712( ...typedData, message: { ...typedData.message, - "Encryption Token": "0x" + userID, + "Encryption Token": "0x" + token, }, }; const signature = await getSignature(account, data); - const auth = await authenticateUser(userID, { + const auth = await authenticateUser(token, { signature, address: account, }); + console.log("🚀 ~ file: ethRequests.ts:166 ~ auth:", auth); return auth; - } catch (error) { + } catch (error: any) { throw error; } } diff --git a/tools/walletextension/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts index 244f246ffb..2e057b3d9d 100644 --- a/tools/walletextension/frontend/src/api/gateway.ts +++ b/tools/walletextension/frontend/src/api/gateway.ts @@ -11,21 +11,21 @@ export async function fetchVersion(): Promise { } export async function accountIsAuthenticated( - userID: string, + token: string, account: string ): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.queryAccountUserID), + url: pathToUrl(apiRoutes.queryAccountToken), searchParams: { - token: userID, + token, a: account, }, }); } export const authenticateUser = async ( - userID: string, + token: string, authenticateFields: { signature: string; address: string; @@ -36,17 +36,17 @@ export const authenticateUser = async ( url: pathToUrl(apiRoutes.authenticate), data: authenticateFields, searchParams: { - token: userID, + token, }, }); }; -export async function revokeAccountsApi(userID: string): Promise { +export async function revokeAccountsApi(token: string): Promise { return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.revoke), searchParams: { - token: userID, + token, }, }); } diff --git a/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx b/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx index e1c661649b..4004c6e149 100644 --- a/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx +++ b/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx @@ -22,7 +22,6 @@ const ConnectWalletButton = () => { <> Connect -  Account(s) )} diff --git a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx index 2ec744042d..f7a1004228 100644 --- a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx @@ -56,9 +56,8 @@ const Disconnected = () => {

By connecting your wallet to Ten and signing the signature request - you will get a unique user id, which is also your{" "} - viewing key. It is contained in the RPC link and unique for - each user. + you will get a unique token, which is also your viewing key + . It is contained in the RPC link and unique for each user.

diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 3678d767c1..c7a507c374 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -14,7 +14,7 @@ import { import { ToastType } from "@/types/interfaces"; import { authenticateAccountWithTenGatewayEIP712, - getUserID, + getToken, } from "@/api/ethRequests"; import { ethers } from "ethers"; import ethService from "@/services/ethService"; @@ -37,7 +37,7 @@ export const WalletConnectionProvider = ({ children, }: WalletConnectionProviderProps) => { const [walletConnected, setWalletConnected] = useState(false); - const [userID, setUserID] = useState(""); + const [token, setToken] = useState(""); const [version, setVersion] = useState(null); const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState(null); @@ -57,75 +57,132 @@ export const WalletConnectionProvider = ({ if (!ethereum) { return; } - const status = await ethService.isUserConnectedToTenChain(userID); + const status = await ethService.isUserConnectedToTenChain(token); + await fetchUserAccounts(); setWalletConnected(status); }; const initialize = async () => { - const providerInstance = new ethers.providers.Web3Provider(ethereum); - setProvider(providerInstance); - await ethService.checkIfMetamaskIsLoaded(providerInstance); - const id = await getUserID(providerInstance); - setUserID(id); - handleAccountsChanged(); - const accounts = await ethService.getAccounts(providerInstance); - setAccounts(accounts || null); - setVersion(await fetchVersion()); - setLoading(false); + if (!ethereum) { + showToast( + ToastType.DESTRUCTIVE, + "Please install Metamask to connect your wallet." + ); + return; + } + try { + const providerInstance = new ethers.providers.Web3Provider(ethereum); + setProvider(providerInstance); + await ethService.checkIfMetamaskIsLoaded(providerInstance); + + const fetchedToken = await getToken(providerInstance); + setToken(fetchedToken); + const status = await ethService.isUserConnectedToTenChain(fetchedToken); + setWalletConnected(status); + + const accounts = await ethService.getAccounts(providerInstance); + setAccounts(accounts || null); + setVersion(await fetchVersion()); + } catch (error) { + showToast( + ToastType.DESTRUCTIVE, + "Error initializing wallet connection. Please refresh the page." + ); + } finally { + setLoading(false); + } }; const connectAccount = async (account: string) => { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to connect an account."); - return; - } - await authenticateAccountWithTenGatewayEIP712(userID, account); - const { status } = await accountIsAuthenticated(userID, account); - if (status) { - showToast(ToastType.SUCCESS, "Account authenticated!"); - setAccounts((accounts) => { - if (!accounts) { - return null; - } - return accounts.map((acc) => { - if (acc.name === account) { - return { - ...acc, - connected: status, - }; + try { + if (!token) { + showToast( + ToastType.INFO, + "Encryption token is required to connect an account." + ); + return; + } + await authenticateAccountWithTenGatewayEIP712(token, account); + const { status } = await accountIsAuthenticated(token, account); + if (status) { + showToast(ToastType.SUCCESS, "Account authenticated!"); + setAccounts((accounts) => { + if (!accounts) { + return null; } - return acc; + return accounts.map((acc) => { + if (acc.name === account) { + return { + ...acc, + connected: status, + }; + } + return acc; + }); }); - }); - } else { + } else { + showToast(ToastType.DESTRUCTIVE, "Account authentication failed."); + } + } catch (error) { showToast(ToastType.DESTRUCTIVE, "Account authentication failed."); } }; const revokeAccounts = async () => { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to revoke accounts"); + if (!token) { + showToast( + ToastType.INFO, + "Encryption token is required to revoke accounts" + ); return; } - const revokeResponse = await revokeAccountsApi(userID); + const revokeResponse = await revokeAccountsApi(token); if (revokeResponse === ToastType.SUCCESS) { showToast(ToastType.DESTRUCTIVE, "Accounts revoked!"); setAccounts(null); setWalletConnected(false); - setUserID(""); + setToken(""); } }; const fetchUserAccounts = async () => { - const accounts = await ethService.getAccounts(provider); - setAccounts(accounts || null); - setWalletConnected(true); + if (!provider) { + showToast( + ToastType.INFO, + "Provider is required to fetch user accounts. Please connect your wallet." + ); + return; + } + + try { + const accounts = await ethService.getAccounts(provider); + const token = await getToken(provider); + setToken(token); + let updatedAccounts: Account[] = []; + + updatedAccounts = await Promise.all( + accounts!.map(async (account) => { + await ethService.authenticateWithGateway(token, account.name); + const { status } = await accountIsAuthenticated(token, account.name); + return { + ...account, + connected: status, + }; + }) + ); + setAccounts(updatedAccounts || null); + } catch (error) { + showToast(ToastType.DESTRUCTIVE, "Error fetching user accounts."); + } finally { + setWalletConnected(true); + setLoading(false); + } }; const walletConnectionContextValue: WalletConnectionContextType = { walletConnected, accounts, - userID, + token, connectAccount, version, revokeAccounts, diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 5b582a40dd..0c22bc6180 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -33,7 +33,7 @@ export const testnetUrls = { }; export const SWITCHED_CODE = 4902; -export const userIDHexLength = 40; +export const tokenHexLength = 40; export const tenGatewayVersion = "v1"; export const tenChainIDDecimal = 443; diff --git a/tools/walletextension/frontend/src/lib/utils.ts b/tools/walletextension/frontend/src/lib/utils.ts index a13f5f2cea..f4d0c7abfd 100644 --- a/tools/walletextension/frontend/src/lib/utils.ts +++ b/tools/walletextension/frontend/src/lib/utils.ts @@ -5,7 +5,7 @@ import { tenChainIDHex, tenGatewayAddress, testnetUrls, - userIDHexLength, + tokenHexLength, } from "./constants"; export function cn(...inputs: ClassValue[]) { @@ -17,8 +17,8 @@ export function formatTimeAgo(unixTimestampSeconds: string) { return formatDistanceToNow(date, { addSuffix: true }); } -export function isValidUserIDFormat(value: string) { - return typeof value === "string" && value.length === userIDHexLength; +export function isValidTokenFormat(value: string) { + return typeof value === "string" && value.length === tokenHexLength; } export function getRandomIntAsString(min: number, max: number) { @@ -27,6 +27,7 @@ export function getRandomIntAsString(min: number, max: number) { const randomInt = Math.floor(Math.random() * (max - min + 1)) + min; return randomInt.toString(); } + export function getNetworkName() { switch (tenGatewayAddress) { case testnetUrls.uat.url: diff --git a/tools/walletextension/frontend/src/routes/index.ts b/tools/walletextension/frontend/src/routes/index.ts index 1b27864791..2bba85e009 100644 --- a/tools/walletextension/frontend/src/routes/index.ts +++ b/tools/walletextension/frontend/src/routes/index.ts @@ -6,7 +6,7 @@ export const NavLinks: NavLink[] = []; export const apiRoutes = { join: `/${tenGatewayVersion}/join/`, authenticate: `/${tenGatewayVersion}/authenticate/`, - queryAccountUserID: `/${tenGatewayVersion}/query/`, + queryAccountToken: `/${tenGatewayVersion}/query/`, revoke: `/${tenGatewayVersion}/revoke/`, version: `/version/`, }; diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts index da1c3ce780..b44de0ccd0 100644 --- a/tools/walletextension/frontend/src/services/ethService.ts +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -1,79 +1,69 @@ +import { ethers } from "ethers"; import { authenticateAccountWithTenGatewayEIP712, - getUserID, + getToken, } from "@/api/ethRequests"; import { accountIsAuthenticated } from "@/api/gateway"; import { showToast } from "@/components/ui/use-toast"; import { METAMASK_CONNECTION_TIMEOUT } from "@/lib/constants"; -import { isTenChain, isValidUserIDFormat, ethereum } from "@/lib/utils"; +import { isTenChain, isValidTokenFormat, ethereum } from "@/lib/utils"; import { ToastType } from "@/types/interfaces"; import { Account } from "@/types/interfaces/WalletInterfaces"; -import { ethers } from "ethers"; const ethService = { checkIfMetamaskIsLoaded: async (provider: ethers.providers.Web3Provider) => { - if (ethereum) { - await ethService.handleEthereum(provider); - } else { - showToast(ToastType.INFO, "Connecting to MetaMask..."); - - let timeoutId: ReturnType; - const handleEthereumOnce = () => { - ethService.handleEthereum(provider); - }; + try { + if (ethereum) { + return await ethService.handleEthereum(provider); + } else { + showToast(ToastType.INFO, "Connecting to MetaMask..."); + + let timeoutId: ReturnType; + + const handleEthereumOnce = async () => { + await ethService.handleEthereum(provider); + }; + + window.addEventListener( + "ethereum#initialized", + () => { + clearTimeout(timeoutId); + handleEthereumOnce(); + }, + { + once: true, + } + ); - window.addEventListener( - "ethereum#initialized", - () => { - clearTimeout(timeoutId); + timeoutId = setTimeout(() => { handleEthereumOnce(); - }, - { - once: true, - } - ); - - timeoutId = setTimeout(() => { - handleEthereumOnce(); // Call the handler function after the timeout - }, METAMASK_CONNECTION_TIMEOUT); + }, METAMASK_CONNECTION_TIMEOUT); + } + } catch (error) { + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + throw error; } }, handleEthereum: async (provider: ethers.providers.Web3Provider) => { - if (ethereum && ethereum.isMetaMask) { - const fetchedUserID = await getUserID(provider); - if (fetchedUserID && isValidUserIDFormat(fetchedUserID)) { - showToast(ToastType.SUCCESS, "MetaMask connected!"); + try { + if (ethereum && ethereum.isMetaMask) { + return; } else { showToast( ToastType.WARNING, - "Please connect to the Ten chain to use Ten Gateway." + "Please install MetaMask to use Ten Gateway." ); } - } else { - showToast( - ToastType.WARNING, - "Please install MetaMask to use Ten Gateway." - ); - } - }, - - fetchUserID: async (provider: ethers.providers.Web3Provider) => { - try { - return await getUserID(provider); - } catch (e: any) { - showToast( - ToastType.DESTRUCTIVE, - `${e.message} ${e.data?.message}` || - "Error: Could not fetch your user ID. Please try again later." - ); - return null; + } catch (error: any) { + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + throw error; } }, - isUserConnectedToTenChain: async (userID: string) => { + isUserConnectedToTenChain: async (token: string) => { if (await isTenChain()) { - if (userID && isValidUserIDFormat(userID)) { + if (token && isValidTokenFormat(token)) { return true; } else { return false; @@ -83,7 +73,11 @@ const ethService = { } }, - getAccounts: async (provider: ethers.providers.Web3Provider) => { + formatAccounts: async ( + accounts: string[], + provider: ethers.providers.Web3Provider, + token: string + ) => { if (!provider) { showToast( ToastType.DESTRUCTIVE, @@ -91,17 +85,35 @@ const ethService = { ); return; } + let updatedAccounts: Account[] = []; + showToast(ToastType.INFO, "Checking account authentication status..."); + const authenticationPromise = accounts.map((account) => + accountIsAuthenticated(token, account).then(({ status }) => { + return { + name: account, + connected: status, + }; + }) + ); + updatedAccounts = await Promise.all(authenticationPromise); + return updatedAccounts; + }, - const id = await getUserID(provider); - - if (!id || !isValidUserIDFormat(id)) { + getAccounts: async (provider: ethers.providers.Web3Provider) => { + if (!provider) { showToast( ToastType.DESTRUCTIVE, - "No user ID found. Please try again later." + "No provider found. Please try again later." ); return; } + const token = await getToken(provider); + + if (!token || !isValidTokenFormat(token)) { + return; + } + try { showToast(ToastType.INFO, "Getting accounts..."); @@ -116,25 +128,25 @@ const ethService = { showToast(ToastType.DESTRUCTIVE, "No MetaMask accounts found."); return []; } + showToast(ToastType.SUCCESS, "Accounts found!"); - let updatedAccounts: Account[] = []; - - const authenticationPromises = accounts.map((account) => - authenticateAccountWithTenGatewayEIP712(id, account) - .then(() => accountIsAuthenticated(id, account)) - .then(({ status }) => ({ - name: account, - connected: status, - })) - ); - updatedAccounts = await Promise.all(authenticationPromises); - showToast(ToastType.SUCCESS, "Accounts fetched successfully."); - return updatedAccounts; + return ethService.formatAccounts(accounts, provider, token); } catch (error) { console.error(error); showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); } }, + + authenticateWithGateway: async (token: string, account: string) => { + try { + return await authenticateAccountWithTenGatewayEIP712(token, account); + } catch (error) { + showToast( + ToastType.DESTRUCTIVE, + `Error authenticating account: ${account}` + ); + } + }, }; export default ethService; diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index 37d27af96c..c2920d9076 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -3,7 +3,7 @@ import { joinTestnet } from "../api/gateway"; import { useWalletConnection } from "../components/providers/wallet-provider"; import { showToast } from "../components/ui/use-toast"; import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; -import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; +import { getRPCFromUrl, isTenChain, isValidTokenFormat } from "../lib/utils"; import { addNetworkToMetaMask, connectAccounts, @@ -11,7 +11,7 @@ import { } from "@/api/ethRequests"; const useGatewayService = () => { - const { userID, provider, fetchUserAccounts, setLoading } = + const { token, provider, fetchUserAccounts, setLoading } = useWalletConnection(); const isMetamaskConnected = async () => { @@ -23,15 +23,15 @@ const useGatewayService = () => { return accounts.length > 0; } catch (error) { showToast(ToastType.DESTRUCTIVE, "Unable to get accounts"); + throw error; } - return false; }; const connectToTenTestnet = async () => { setLoading(true); try { if (await isTenChain()) { - if (!userID || !isValidUserIDFormat(userID)) { + if (!token || !isValidTokenFormat(token)) { showToast( ToastType.DESTRUCTIVE, "Existing Ten network detected in MetaMask. Please remove before hitting begin" @@ -42,10 +42,7 @@ const useGatewayService = () => { const switched = await switchToTenNetwork(); - if ( - switched === SWITCHED_CODE || - (userID && !isValidUserIDFormat(userID)) - ) { + if (switched === SWITCHED_CODE || (token && !isValidTokenFormat(token))) { const user = await joinTestnet(); const rpcUrls = [ `${getRPCFromUrl()}/${tenGatewayVersion}/?token=${user}`, diff --git a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts index 3ca123984a..7456a4ff39 100644 --- a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts @@ -4,7 +4,7 @@ export interface WalletConnectionContextType { accounts: Account[] | null; walletConnected: boolean; connectAccount: (account: string) => Promise; - userID: string | null; + token: string | null; version: string | null; revokeAccounts: () => void; loading: boolean; @@ -13,10 +13,6 @@ export interface WalletConnectionContextType { setLoading: (loading: boolean) => void; } -export interface Props { - children: React.ReactNode; -} - export interface State { hasError: boolean; }