From d4288b3a0ffa2aaae51f94124345d3e5fd26aa11 Mon Sep 17 00:00:00 2001 From: Jigar Patel Date: Mon, 14 Oct 2024 21:59:19 -0400 Subject: [PATCH] changed wallet connect from wallet status to wallet provider --- apps/provider-console/next.config.js | 5 +- apps/provider-console/sentry.client.config.js | 7 ++ .../become-provider/WalletImport.tsx | 14 ++- .../src/components/home/HomeContainer.tsx | 33 ++--- .../src/components/layout/WalletStatus.tsx | 8 -- .../components/wallet/ConnectWalletButton.tsx | 6 +- .../CustomChainProvider.tsx | 2 +- .../context/WalletProvider/WalletProvider.tsx | 116 +++++++++++++++++- apps/provider-console/src/utils/authClient.ts | 31 +++-- apps/provider-console/src/utils/restClient.ts | 23 +++- .../provider-console/src/utils/walletUtils.ts | 5 + 11 files changed, 191 insertions(+), 59 deletions(-) diff --git a/apps/provider-console/next.config.js b/apps/provider-console/next.config.js index 5b412413d..9bd19b204 100644 --- a/apps/provider-console/next.config.js +++ b/apps/provider-console/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { withSentryConfig } = require('@sentry/nextjs'); +const { withSentryConfig } = require("@sentry/nextjs"); const nextConfig = { reactStrictMode: false, compiler: { @@ -33,8 +33,7 @@ const nextConfig = { }); config.externals.push("pino-pretty"); return config; - }, + } }; module.exports = withSentryConfig(nextConfig); - diff --git a/apps/provider-console/sentry.client.config.js b/apps/provider-console/sentry.client.config.js index 3bcd363ab..665eceea7 100644 --- a/apps/provider-console/sentry.client.config.js +++ b/apps/provider-console/sentry.client.config.js @@ -6,3 +6,10 @@ Sentry.init({ replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0 }); + +const originalConsoleError = console.error; + +console.error = (...args) => { + Sentry.captureMessage(args.join(" "), "error"); + originalConsoleError(...args); +}; diff --git a/apps/provider-console/src/components/become-provider/WalletImport.tsx b/apps/provider-console/src/components/become-provider/WalletImport.tsx index dd2bea3ac..55faad3a6 100644 --- a/apps/provider-console/src/components/become-provider/WalletImport.tsx +++ b/apps/provider-console/src/components/become-provider/WalletImport.tsx @@ -75,6 +75,7 @@ export const WalletImport: React.FunctionComponent = ({ stepC const [showSeedForm, setShowSeedForm] = useState(false); const [isMounted, setIsMounted] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const [providerProcess, setProviderProcess] = useAtom(providerProcessStore.providerProcessAtom); @@ -96,6 +97,7 @@ export const WalletImport: React.FunctionComponent = ({ stepC const submitForm = async (data: SeedFormValues) => { setIsLoading(true); + setError(null); // Reset error state try { if (providerProcess.machines && providerProcess.machines.length > 0) { const publicKey = providerProcess.machines[0].systemInfo.public_key; @@ -137,14 +139,15 @@ export const WalletImport: React.FunctionComponent = ({ stepC } })); stepChange(); + } else { + throw new Error("Invalid response from server"); } } else { - console.error("No machine information available"); - // Handle the case when machine information is not available + throw new Error("No machine information available"); } } catch (error) { console.error("Error during wallet verification:", error); - // Handle any errors that occurred during the process + setError("An error occurred while processing your request. Please try again."); } finally { setIsLoading(false); } @@ -277,6 +280,11 @@ export const WalletImport: React.FunctionComponent = ({ stepC {isLoading ? "Loading..." : "Next"} + {error && ( +
+

{error.message || "An error occurred during wallet import."}

+
+ )} diff --git a/apps/provider-console/src/components/home/HomeContainer.tsx b/apps/provider-console/src/components/home/HomeContainer.tsx index 875447b4b..59cabbfcc 100644 --- a/apps/provider-console/src/components/home/HomeContainer.tsx +++ b/apps/provider-console/src/components/home/HomeContainer.tsx @@ -16,44 +16,27 @@ import providerProcessStore from "@src/store/providerProcessStore"; export function HomeContainer() { const [, resetProcess] = useAtom(providerProcessStore.resetProviderProcess); const router = useRouter(); - const { isWalletConnected, isWalletArbitrarySigned } = useWallet(); + const { isWalletConnected, isWalletArbitrarySigned, isProvider, isOnline, isProviderStatusFetched } = useWallet(); const [isLoading, setIsLoading] = useState(false); - const [isProvider, setIsProvider] = useState(false); const [, setProvider] = useState(null); - const [isOnline, setIsOnline] = useState(false); const [actions, setActions] = useState(null); const selectedNetwork = useAtomValue(networkStore.selectedNetwork); // or similar method to get the value const [loadingMessage, setLoadingMessage] = useState(null); useEffect(() => { - if (isWalletConnected) { + if (isProvider && isOnline) { setIsLoading(true); - if (isWalletArbitrarySigned) { - fetchProviderStatus(); - } + fetchActions(); } - }, [isWalletConnected, isWalletArbitrarySigned]); + console.log("isProviderStatusFetched", isProviderStatusFetched); + }, [isProvider, isOnline]); - const fetchProviderStatus = async () => { + const fetchActions = async () => { try { - setLoadingMessage("Checking provider status..."); - const isProviderResponse: any = await restClient.get(`/provider/status/onchain?chainid=${selectedNetwork.chainId}`); - setIsProvider(isProviderResponse.provider ? true : false); - setProvider(isProviderResponse.provider); - - if (isProviderResponse.provider) { - setLoadingMessage("Provider found, Checking online status..."); - const isOnlineResponse: any = await restClient.get(`/provider/status/online?chainid=${selectedNetwork.chainId}`); - setIsOnline(isOnlineResponse.online); - } - setLoadingMessage("Checking actions..."); const actionsResponse: any = await restClient.get(`/actions`); setActions(actionsResponse.actions); } catch (error) { - setLoadingMessage("Error fetching provider status"); - } finally { - setIsLoading(false); - setLoadingMessage(null); + setLoadingMessage("Error fetching actions"); } }; @@ -69,7 +52,7 @@ export function HomeContainer() { }; return ( - +
{isLoading ? ( diff --git a/apps/provider-console/src/components/layout/WalletStatus.tsx b/apps/provider-console/src/components/layout/WalletStatus.tsx index 4efb070c6..e36876f4f 100644 --- a/apps/provider-console/src/components/layout/WalletStatus.tsx +++ b/apps/provider-console/src/components/layout/WalletStatus.tsx @@ -80,14 +80,6 @@ export function WalletStatus() { } }; - useEffect(() => { - if (isWalletConnected && address) { - handleWalletConnectSuccess(); - } else if (!isWalletConnected) { - console.log("Wallet disconnected"); - } - }, [isWalletConnected, address]); - const onDisconnectClick = () => logout(); const WalletInfo = () => ( diff --git a/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx b/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx index e1acc9656..4d24b89dc 100644 --- a/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx +++ b/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx @@ -5,6 +5,7 @@ import { Wallet } from "iconoir-react"; import { useSelectedChain } from "@src/context/CustomChainProvider"; import { cn } from "@src/utils/styleUtils"; +import { useWallet } from "@src/context/WalletProvider"; interface Props extends ButtonProps { children?: ReactNode; @@ -12,7 +13,8 @@ interface Props extends ButtonProps { } export const ConnectWalletButton: React.FunctionComponent = ({ className = "", ...rest }) => { - const { connect, status, isWalletConnected, address } = useSelectedChain(); + const { status, isWalletConnected, address } = useSelectedChain(); + const { connectWallet } = useWallet(); // Define your custom function to call on successful connection const onWalletConnectSuccess = () => { @@ -29,7 +31,7 @@ export const ConnectWalletButton: React.FunctionComponent = ({ className }, [status, address]); // Ensure to include address as a dependency if needed return ( - diff --git a/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx b/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx index c961a62e0..920f8164b 100644 --- a/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx +++ b/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx @@ -46,7 +46,7 @@ export function CustomChainProvider({ children }: Props) { }} signerOptions={{ preferredSignType: () => "direct", - signingStargate: () => ({ + signingStargate: (): any => ({ registry: customRegistry, gasPrice: GasPrice.fromString("0.025uakt") }) diff --git a/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx b/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx index 23619b01c..ca6f35c72 100644 --- a/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx +++ b/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx @@ -4,10 +4,11 @@ import { useEffect, useState } from "react"; import { Snackbar } from "@akashnetwork/ui/components"; import { EncodeObject } from "@cosmjs/proto-signing"; import { SigningStargateClient } from "@cosmjs/stargate"; -import { useManager } from "@cosmos-kit/react"; +import { useChainWallet, useManager } from "@cosmos-kit/react"; import axios from "axios"; import { useRouter } from "next/navigation"; import { SnackbarKey, useSnackbar } from "notistack"; +import { useSelectedNetwork } from "@src/hooks/useSelectedNetwork"; import { TransactionModal } from "@src/components/layout/TransactionModal"; import { useUsdcDenom } from "@src/hooks/useDenom"; @@ -20,6 +21,9 @@ import { useSelectedChain } from "../CustomChainProvider"; import { useSettings } from "../SettingsProvider"; import { jwtDecode } from "jwt-decode"; import { checkAndRefreshToken } from "@src/utils/tokenUtils"; +import restClient from "@src/utils/restClient"; +import { getNonceMessage, leapSignArbitrary, keplrSignArbitrary } from "@src/utils/walletUtils"; +import authClient from "@src/utils/authClient"; type Balances = { uakt: number; @@ -39,6 +43,11 @@ type ContextType = { setIsWalletArbitrarySigned: React.Dispatch>; signAndBroadcastTx: (msgs: EncodeObject[]) => Promise; refreshBalances: (address?: string) => Promise; + isProvider: boolean; + isOnline: boolean; + provider: any; // Replace 'any' with a more specific type if available + isProviderStatusFetched: boolean; + handleArbitrarySigning: () => Promise; }; const WalletProviderContext = React.createContext({} as ContextType); @@ -46,6 +55,10 @@ const WalletProviderContext = React.createContext({} as ContextType export const WalletProvider = ({ children }) => { const [walletBalances, setWalletBalances] = useState(null); const [isWalletLoaded, setIsWalletLoaded] = useState(true); + const [isWalletProvider, setIsWalletProvider] = useState(false); + const [isWalletProviderOnline, setIsWalletProviderOnline] = useState(false); + const [provider, setProvider] = useState(null); + const [isProviderStatusFetched, setIsProviderStatusFetched] = useState(false); const [isBroadcastingTx, setIsBroadcastingTx] = useState(false); const [isWaitingForApproval, setIsWaitingForApproval] = useState(false); const [isWalletArbitrarySigned, setIsWalletArbitrarySigned] = useState(false); @@ -54,8 +67,23 @@ export const WalletProvider = ({ children }) => { const router = useRouter(); const { settings } = useSettings(); const usdcIbcDenom = useUsdcDenom(); - const { disconnect, getOfflineSigner, isWalletConnected, address: walletAddress, connect, username, estimateFee, sign, broadcast } = useSelectedChain(); + const { + disconnect, + getOfflineSigner, + isWalletConnected, + address: walletAddress, + connect, + username, + estimateFee, + sign, + broadcast, + wallet, + signArbitrary + } = useSelectedChain(); const { addEndpoints } = useManager(); + const selectedNetwork = useSelectedNetwork(); + // const { signArbitrary: keplrSignArbitrary } = useChainWallet("akash", "keplr-extension"); + // const { signArbitrary: leapSignArbitrary } = useChainWallet("akash", "leap-extension"); // const { // fee: { default: feeGranter } // } = useAllowance(); @@ -80,6 +108,10 @@ export const WalletProvider = ({ children }) => { if (validAccessToken) { console.log("Access token is valid"); setIsWalletArbitrarySigned(true); + await fetchProviderStatus(); + + await fetchProviderStatus(); + setIsProviderStatusFetched(true); } else { console.log("No valid access token found"); setIsWalletArbitrarySigned(false); @@ -93,6 +125,20 @@ export const WalletProvider = ({ children }) => { })(); }, [settings?.rpcEndpoint, isWalletConnected]); + const fetchProviderStatus = async () => { + try { + const isProviderResponse: any = await restClient.get(`/provider/status/onchain?chainid=${selectedNetwork.chainId}`); + setIsWalletProvider(isProviderResponse.provider ? true : false); + setProvider(isProviderResponse.provider); + if (isProviderResponse.provider) { + const isOnlineResponse: any = await restClient.get(`/provider/status/online?chainid=${selectedNetwork.chainId}`); + setIsWalletProviderOnline(isOnlineResponse.online); + } + } catch (error) { + console.error("Error fetching provider status:", error); + } + }; + async function createStargateClient() { const selectedNetwork = getSelectedNetwork(); @@ -130,25 +176,78 @@ export const WalletProvider = ({ children }) => { localStorage.removeItem("walletAddress"); setWalletBalances(null); disconnect(); + setIsWalletArbitrarySigned(false); + setIsProviderStatusFetched(false); + setIsWalletProvider(false); + setIsWalletProviderOnline(false); + setProvider(null); router.push(UrlService.home()); } async function connectWallet() { console.log("Connecting wallet with CosmosKit..."); - connect(); + await connect(); + // console.log("Connected wallet with CosmosKit"); + // await loadWallet(); + // await handleArbitrarySigning(); + // console.log("Wallet address", walletAddress); + } - await loadWallet(); + async function handleArbitrarySigning() { + console.log("Access token", localStorage.getItem("accessToken")); + console.log("Wallet address", walletAddress); + if (!localStorage.getItem("accessToken") && walletAddress) { + console.log("Handling arbitrary signing"); + try { + const response: any = await authClient.get(`users/nonce/${walletAddress}`); + if (response?.data?.nonce) { + const message = getNonceMessage(response.data.nonce, walletAddress); + + // const signArbitrary = username === "leap-extension" ? leapSignArbitrary : keplrSignArbitrary; + // const signArbitrary = wallet?.name === "leap-extension" ? leapSignArbitrary : keplrSignArbitrary; + + const result = await signArbitrary(walletAddress, message); + + if (result) { + const verifySign = await authClient.post("auth/verify", { signer: walletAddress, ...result }); + if (verifySign.data) { + localStorage.setItem("accessToken", verifySign.data.access_token); + localStorage.setItem("refreshToken", verifySign.data.refresh_token); + localStorage.setItem("walletAddress", walletAddress); + setIsWalletArbitrarySigned(true); + } else { + throw new Error("Verification failed"); + } + } else { + throw new Error("Signing failed"); + } + } else { + if (response.status === "error" && response.error.code === "N4040") { + await authClient.post("users", { address: walletAddress }); + await handleArbitrarySigning(); + } else { + throw new Error("Invalid nonce response"); + } + } + } catch (error) { + console.error("Error during arbitrary signing:", error); + logout(); + setIsWalletArbitrarySigned(false); + } + } } // Update balances on wallet address change useEffect(() => { if (walletAddress) { loadWallet(); + handleArbitrarySigning(); } }, [walletAddress]); async function loadWallet(): Promise { const selectedNetwork = getSelectedNetwork(); + console.log("selectedNetwork", selectedNetwork); const storageWallets = JSON.parse(localStorage.getItem(`${selectedNetwork.id}/wallets`) || "[]") as LocalWalletDataType[]; let currentWallets = storageWallets ?? []; @@ -269,6 +368,8 @@ export const WalletProvider = ({ children }) => { const _address = address || walletAddress; const client = await getStargateClient(); + console.log("client", client); + // console.log("address", _address); if (client) { const balances = await client.getAllBalances(_address as string); const uaktBalance = balances.find(b => b.denom === uAktDenom); @@ -304,7 +405,12 @@ export const WalletProvider = ({ children }) => { logout, setIsWalletLoaded, signAndBroadcastTx, - refreshBalances + refreshBalances, + isProvider: isWalletProvider, + isOnline: isWalletProviderOnline, + provider: provider, + isProviderStatusFetched, + handleArbitrarySigning }} > {children} diff --git a/apps/provider-console/src/utils/authClient.ts b/apps/provider-console/src/utils/authClient.ts index 283f69343..67048b873 100644 --- a/apps/provider-console/src/utils/authClient.ts +++ b/apps/provider-console/src/utils/authClient.ts @@ -1,5 +1,6 @@ // import { notification } from 'antd' import axios from "axios"; +import * as Sentry from "@sentry/nextjs"; const errorNotification = (error = "Error Occurred") => { // notification.error({ @@ -18,22 +19,30 @@ authClient.interceptors.response.use( return response.data; }, error => { - // whatever you want to do with the error + let errorMessage = "An unexpected error occurred"; + if (typeof error.response === "undefined") { - errorNotification("Server is not reachable or CORS is not enable on the server!"); + errorMessage = "Server is not reachable or CORS is not enabled on the server!"; } else if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - errorNotification("Server Error!"); + errorMessage = "Server Error!"; } else if (error.request) { - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js - errorNotification("Server is not responding!"); + errorMessage = "Server is not responding!"; } else { - // Something happened in setting up the request that triggered an Error - errorNotification(error.message); + errorMessage = error.message; } + + // Log the error to Sentry + Sentry.captureException(error, { + extra: { + errorMessage, + requestUrl: error.config?.url, + requestMethod: error.config?.method, + }, + }); + + // Display error notification (you can uncomment and use your preferred notification method) + errorNotification(errorMessage); + throw error; } ); diff --git a/apps/provider-console/src/utils/restClient.ts b/apps/provider-console/src/utils/restClient.ts index 56a72c0d3..41c596d76 100644 --- a/apps/provider-console/src/utils/restClient.ts +++ b/apps/provider-console/src/utils/restClient.ts @@ -1,5 +1,6 @@ // import { notification } from "antd"; import axios from "axios"; +import * as Sentry from "@sentry/nextjs"; import authClient from "./authClient"; import { checkAndRefreshToken } from "./tokenUtils"; @@ -18,6 +19,9 @@ restClient.interceptors.response.use( return response.data; }, async error => { + // Capture the error with Sentry + Sentry.captureException(error); + // whatever you want to do with the error if (typeof error.response === "undefined") { errorNotification("Server is not reachable or CORS is not enable on the server!"); @@ -57,7 +61,24 @@ restClient.interceptors.response.use( // history.push("/auth/login"); } - errorNotification("Server Error!"); + // Add more specific error handling + if (error.response.status >= 400 && error.response.status < 500) { + Sentry.setContext("api_error", { + status: error.response.status, + data: error.response.data, + url: error.config.url, + method: error.config.method, + }); + errorNotification(`Client Error: ${error.response.status}`); + } else if (error.response.status >= 500) { + Sentry.setContext("api_error", { + status: error.response.status, + data: error.response.data, + url: error.config.url, + method: error.config.method, + }); + errorNotification(`Server Error: ${error.response.status}`); + } } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of diff --git a/apps/provider-console/src/utils/walletUtils.ts b/apps/provider-console/src/utils/walletUtils.ts index 8daa3ba7f..7decc3651 100644 --- a/apps/provider-console/src/utils/walletUtils.ts +++ b/apps/provider-console/src/utils/walletUtils.ts @@ -73,3 +73,8 @@ export function updateLocalStorageWalletName(address: string, name: string) { return { ...wallet, name }; }); } + +export function getNonceMessage(nonce: string, walletAddress: string) { + // const url = process.env.NODE_ENV === "development" ? DEV_URL : window.location.hostname; + return `provider-beta.console.akash.network wants you to sign in with your Keplr account - ${walletAddress} using Nonce - ${nonce}`; +} \ No newline at end of file