diff --git a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC1155Form.tsx b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC1155Form.tsx index 4f86556..019a801 100644 --- a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC1155Form.tsx +++ b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC1155Form.tsx @@ -11,6 +11,8 @@ import { AddressInput, InputBase } from "~~/components/scaffold-eth"; import { ERC1155Tx } from "~~/types/business"; import { ERC1155_ABI } from "~~/utils/constants"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; + const erc1155Interface = new ethers.utils.Interface(ERC1155_ABI); @@ -18,10 +20,10 @@ export const ERC1155Form = ({ hackedAddress, safeAddress, addAsset, close }: ITo const [contractAddress, setContractAddress] = useState(""); const [tokenIds, setTokenIds] = useState(""); const publicClient = usePublicClient({ chainId: getTargetNetwork().id }); - + const {showError} = useShowError() const addErc1155TxToBasket = async () => { if (!isAddress(contractAddress) || !tokenIds) { - alert("Provide an ERC1155 contract and token IDs"); + showError("Provide an ERC1155 contract and token IDs"); return; } @@ -44,7 +46,7 @@ export const ERC1155Form = ({ hackedAddress, safeAddress, addAsset, close }: ITo } } if (tokenIdsWithInvalidBalances.length > 0) { - alert( + showError( `Remove following tokenIds as hacked account does not own them: ${tokenIdsWithInvalidBalances.toString()}`, ); return; @@ -73,7 +75,7 @@ export const ERC1155Form = ({ hackedAddress, safeAddress, addAsset, close }: ITo addAsset(newErc1155Tx); } catch (e) { console.error(e); - alert(`Couldn't read the contract. See the console for the error.`); + showError(`Couldn't read the contract. See the console for the error.`); return; } }; diff --git a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC20From.tsx b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC20From.tsx index f038bdf..90ed600 100644 --- a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC20From.tsx +++ b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC20From.tsx @@ -11,12 +11,13 @@ import { AddressInput } from "~~/components/scaffold-eth"; import { ERC20Tx } from "~~/types/business"; import { ERC20_ABI } from "~~/utils/constants"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; const erc20Interface = new ethers.utils.Interface(ERC20_ABI); export const ERC20Form = ({ hackedAddress, safeAddress, addAsset, close }: ITokenForm) => { const [contractAddress, setContractAddress] = useState(""); - + const {showError} = useShowError() let erc20Balance: string = "NO INFO"; try { let { data } = useContractRead({ @@ -35,14 +36,15 @@ export const ERC20Form = ({ hackedAddress, safeAddress, addAsset, close }: IToke // Most probably the contract address is not valid as user is // still typing, so ignore. } + const addErc20TxToBasket = (balance: string) => { if (!isAddress(contractAddress)) { - alert("Provide a contract first"); + showError("Provide a contract first"); return; } if (balance == "NO INFO") { - alert("Hacked account has no balance in given erc20 contract"); + showError("Hacked account has no balance in given erc20 contract"); return; } diff --git a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC721Form.tsx b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC721Form.tsx index ebfbafa..34e2993 100644 --- a/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC721Form.tsx +++ b/packages/nextjs/components/Processes/BundlingProcess/Steps/AssetSelectionStep/ManualAssetSelection/BasicFlow/ERC721Form.tsx @@ -11,6 +11,8 @@ import { AddressInput, InputBase } from "~~/components/scaffold-eth"; import { ERC721Tx } from "~~/types/business"; import { ERC721_ABI } from "~~/utils/constants"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; + const erc721Interface = new ethers.utils.Interface(ERC721_ABI); @@ -18,10 +20,10 @@ export const ERC721Form = ({ hackedAddress, safeAddress, addAsset, close }: ITok const [contractAddress, setContractAddress] = useState(""); const [tokenId, setTokenId] = useState(""); const publicClient = usePublicClient({ chainId: getTargetNetwork().id }); - + const {showError} = useShowError() const addErc721TxToBasket = async () => { if (!isAddress(contractAddress) || !tokenId) { - alert("Provide a contract and a token ID"); + showError("Provide a contract and a token ID"); return; } @@ -36,7 +38,7 @@ export const ERC721Form = ({ hackedAddress, safeAddress, addAsset, close }: ITok } catch (e) {} if (!ownerOfGivenTokenId || ownerOfGivenTokenId.toString() != hackedAddress) { - alert(`Couldn't verify hacked account's ownership. Cannot add to the basket...`); + showError(`Couldn't verify hacked account's ownership. Cannot add to the basket...`); return; } diff --git a/packages/nextjs/components/Processes/BundlingProcess/Steps/HackedAddressStep/HackedAddressStep.tsx b/packages/nextjs/components/Processes/BundlingProcess/Steps/HackedAddressStep/HackedAddressStep.tsx index 91359b7..966cb4c 100644 --- a/packages/nextjs/components/Processes/BundlingProcess/Steps/HackedAddressStep/HackedAddressStep.tsx +++ b/packages/nextjs/components/Processes/BundlingProcess/Steps/HackedAddressStep/HackedAddressStep.tsx @@ -4,6 +4,7 @@ import { isAddress } from "ethers/lib/utils"; import { motion } from "framer-motion"; import { CustomButton } from "~~/components/CustomButton/CustomButton"; import { AddressInput } from "~~/components/scaffold-eth"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; interface IProps { isVisible: boolean; @@ -11,20 +12,20 @@ interface IProps { onSubmit: (address: string) => void; } export const HackedAddressStep = ({ isVisible, safeAddress, onSubmit }: IProps) => { - if (!isVisible) { - return <>; - } - + const {showError} = useShowError(); const [hackedAddress, setHackedAddressCore] = useState(""); const setHackedAddress = (hackedAddress: string) => { if (safeAddress == hackedAddress) { - //TODO: modal - alert("Cannot select safe and hacked addresses the same"); + showError("Cannot select safe and hacked addresses the same"); setHackedAddressCore(""); return; } setHackedAddressCore(hackedAddress); }; + + if (!isVisible) { + return <>; + } return ( @@ -43,7 +44,7 @@ export const HackedAddressStep = ({ isVisible, safeAddress, onSubmit }: IProps) text={"Continue"} onClick={() => { if (!isAddress(hackedAddress)) { - alert("Given hacked address is not a valid address"); + showError("Given hacked address is not a valid address"); return; } onSubmit(hackedAddress); diff --git a/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx b/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx index ff4bcb3..d8d33a5 100644 --- a/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx +++ b/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx @@ -1,5 +1,6 @@ import React from "react"; import { CustomPortal } from "~~/components/CustomPortal/CustomPortal"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; import ClockSvg from "~~/public/assets/flashbotRecovery/clock.svg"; import LogoSvg from "~~/public/assets/flashbotRecovery/logo.svg"; import SuccessSvg from "~~/public/assets/flashbotRecovery/success.svg"; @@ -16,6 +17,7 @@ interface IProps { hackedAddress: string; blockCountdown: number; } + export const RecoveryProcess = ({ recoveryStatus, startSigning, @@ -26,12 +28,14 @@ export const RecoveryProcess = ({ safeAddress, hackedAddress, }: IProps) => { + const {showError} = useShowError(); + if (recoveryStatus == RecoveryProcessStatus.INITIAL) { return <>; } if (recoveryStatus == RecoveryProcessStatus.GAS_PAID) { - alert("you already covered the gas. If you're in a confussy situation, clear cookies and refresh page."); + showError("you already covered the gas. If you're in a confussy situation, clear cookies and refresh page."); return; } diff --git a/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx index fa8ba75..8746ba6 100644 --- a/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx @@ -3,6 +3,7 @@ import { AbiFunction } from "abitype"; import { ethers } from "ethers"; import { FunctionFragment } from "ethers/lib/utils"; import { Address } from "viem"; + import { ContractInput, IntegerInput, @@ -13,6 +14,7 @@ import { } from "~~/components/scaffold-eth"; import { CustomTx, RecoveryTx } from "~~/types/business"; import { notification } from "~~/utils/scaffold-eth"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; type WriteOnlyFunctionFormProps = { abiFunction: AbiFunction; @@ -31,6 +33,8 @@ export const CustomContractWriteForm = ({ hackedAddress, contractAddress, }: WriteOnlyFunctionFormProps) => { + const {showError} = useShowError(); + const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); const [txValue, setTxValue] = useState(""); @@ -74,7 +78,7 @@ export const CustomContractWriteForm = ({ onClick={async () => { try { if (!fragmentString) { - alert("refresh page and try again"); + showError("refresh page and try again"); return; } diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useAutodetectAssets.ts b/packages/nextjs/hooks/flashbotRecoveryBundle/useAutodetectAssets.ts index afbb7f2..a568ff0 100644 --- a/packages/nextjs/hooks/flashbotRecoveryBundle/useAutodetectAssets.ts +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useAutodetectAssets.ts @@ -14,6 +14,7 @@ import { } from "~~/types/business"; import { ERC20_ABI, ERC721_ABI, ERC1155_ABI } from "~~/utils/constants"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "./useShowError"; const erc20Interface = new ethers.utils.Interface(ERC20_ABI); const erc721Interface = new ethers.utils.Interface(ERC721_ABI); @@ -24,6 +25,7 @@ export const useAutodetectAssets = () => { [account: string]: RecoveryTx[]; }>("autoDetectedAssets", {}); + const {showError} = useShowError(); const targetNetwork = getTargetNetwork(); const [alchemy] = useState( new Alchemy({ @@ -51,6 +53,7 @@ export const useAutodetectAssets = () => { .map(res => res.transfers) .flat(); + const getAutodetectedAssets = async ( hackedAddress: string, safeAddress: string, @@ -65,7 +68,7 @@ export const useAutodetectAssets = () => { return; } if (!alchemy) { - alert("Seems Alchemy API rate limit has been reached. Contact irbozk@gmail.com"); + showError("Seems Alchemy API rate limit has been reached. Contact irbozk@gmail.com"); return; } diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts b/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts index 34408b1..a55d62e 100644 --- a/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts @@ -3,6 +3,7 @@ import { BigNumber, ethers } from "ethers"; import { usePublicClient } from "wagmi"; import { RecoveryTx } from "~~/types/business"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "./useShowError"; const BLOCKS_IN_THE_FUTURE: { [i: number]: number } = { 1: 7, @@ -12,7 +13,7 @@ const BLOCKS_IN_THE_FUTURE: { [i: number]: number } = { export const useGasEstimation = () => { const targetNetwork = getTargetNetwork(); const publicClient = usePublicClient({ chainId: targetNetwork.id }); - + const {showError} = useShowError(); const estimateTotalGasPrice = async (txs: RecoveryTx[], deleteTransaction: (id: number) => void) => { const tempProvider = new ethers.providers.InfuraProvider(targetNetwork.id, "416f5398fa3d4bb389f18fd3fa5fb58c"); try { @@ -32,13 +33,14 @@ export const useGasEstimation = () => { }), ); + return estimates .reduce((acc: BigNumber, val: BigNumber) => acc.add(val), BigNumber.from("0")) .mul(await maxBaseFeeInFuture()) .mul(105) .div(100); } catch (e) { - alert( + showError( "Error estimating gas prices. Something can be wrong with one of the transactions. Check the console and remove problematic tx.", ); console.error(e); diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts b/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts index 9bfc006..4b9033f 100644 --- a/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts @@ -7,6 +7,7 @@ import { useAccount, usePublicClient, useWalletClient } from "wagmi"; import { RecoveryTx } from "~~/types/business"; import { RecoveryProcessStatus } from "~~/types/enums"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useShowError } from "./useShowError"; interface IStartProcessPops { safeAddress: string; @@ -31,6 +32,7 @@ export const useRecoveryProcess = () => { const [sentTxHash, setSentTxHash] = useLocalStorage("sentTxHash", ""); const [sentBlock, setSentBlock] = useLocalStorage("sentBlock", 0); const [blockCountdown, setBlockCountdown] = useLocalStorage("blockCountdown", 0); + const {showError} = useShowError(); const [stepActive, setStepActive] = useState(RecoveryProcessStatus.INITIAL); const publicClient = usePublicClient({ chainId: targetNetwork.id }); @@ -53,6 +55,7 @@ export const useRecoveryProcess = () => { })(); }, [targetNetwork.id]); + useInterval(async () => { const isNotAbleToListenBundle = stepActive != RecoveryProcessStatus.LISTEN_BUNDLE || !sentTxHash || sentBlock == 0; try { @@ -63,7 +66,7 @@ export const useRecoveryProcess = () => { setBlockCountdown(blockDelta); if (blockDelta < 0) { - alert("Error, try again"); + showError("Error, try again"); setSentBlock(0); setSentTxHash(""); resetStatus(); @@ -122,7 +125,7 @@ export const useRecoveryProcess = () => { surpass: boolean = false, ) => { if (!surpass && !gasCovered) { - alert("How did you come here without covering the gas fee first??"); + showError("How did you come here without covering the gas fee first??"); resetStatus(); return; } @@ -141,14 +144,14 @@ export const useRecoveryProcess = () => { setGasCovered(false); await sendBundle(currentBundleId); } catch (e) { - alert(`FAILED TO SIGN TXS Error: ${e}`); + showError(`FAILED TO SIGN TXS Error: ${e}`); resetStatus(); } }; const sendBundle = async (currentBundleId: string) => { if (!flashbotsProvider) { - alert("Flashbot provider not available"); + showError("Flashbot provider not available"); resetStatus(); return; } @@ -160,7 +163,7 @@ export const useRecoveryProcess = () => { ) ).json(); if (!finalBundle || !finalBundle.rawTxs) { - alert("Couldn't fetch latest bundle"); + showError("Couldn't fetch latest bundle"); resetStatus(); return; } @@ -188,14 +191,14 @@ export const useRecoveryProcess = () => { console.error(e); setSentTxHash(""); setSentBlock(0); - alert("Error submitting bundles. Check console for details."); + showError("Error submitting bundles. Check console for details."); resetStatus(); } } catch (e) { console.error(e); setSentTxHash(""); setSentBlock(0); - alert("Error submitting bundles. Check console for details."); + showError("Error submitting bundles. Check console for details."); resetStatus(); } }; @@ -223,7 +226,7 @@ export const useRecoveryProcess = () => { return; } catch (e) { resetStatus(); - alert(`Error while adding a custom RPC and signing the funding transaction with the safe account. Error: ${e}`); + showError(`Error while adding a custom RPC and signing the funding transaction with the safe account. Error: ${e}`); } }; diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useShowError.tsx b/packages/nextjs/hooks/flashbotRecoveryBundle/useShowError.tsx new file mode 100644 index 0000000..e4e4490 --- /dev/null +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useShowError.tsx @@ -0,0 +1,43 @@ + +import React, { useState } from "react"; +import { createContext, useContext } from "react"; + +interface IErrorContext { + error: string; + setError: (arg: string) => void; +} +const initalValue: IErrorContext = { error: "", setError: () => ({})}; +export const ErrorContext = createContext(initalValue); + +export const ErrorProvider = ({ children }: any) => { + const [error, setError] = useState(""); + + + return ( + setError(newErr), + }} + > + {children} + + ); +}; + + + +export const useShowError = () => { + const { error, setError } = useContext(ErrorContext); + const resetError = () => { + setError(""); + }; + const showError = (newError: string) => { + setError(newError); + }; + return { + error, + resetError, + showError, + }; +}; diff --git a/packages/nextjs/pages/_app.tsx b/packages/nextjs/pages/_app.tsx index 6f95bef..49e3a4b 100644 --- a/packages/nextjs/pages/_app.tsx +++ b/packages/nextjs/pages/_app.tsx @@ -13,6 +13,7 @@ import { useGlobalState } from "~~/services/store/store"; import { wagmiConfig } from "~~/services/web3/wagmiConfig"; import { appChains } from "~~/services/web3/wagmiConnectors"; import "~~/styles/globals.css"; +import { ErrorProvider } from "~~/hooks/flashbotRecoveryBundle/useShowError"; const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { const price = useNativeCurrencyPrice(); @@ -39,14 +40,16 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { avatar={BlockieAvatar} theme={isDarkTheme ? darkTheme() : lightTheme()} > -
- {/*
*/} -
- -
-
-
- + +
+ {/*
*/} +
+ +
+
+
+ +
); diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index b67f0b4..2ecc0ff 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -5,11 +5,14 @@ import { NextPage } from "next"; import { useLocalStorage } from "usehooks-ts"; import { useAccount } from "wagmi"; import { CustomHeader } from "~~/components/CustomHeader/CustomHeader"; +import { CustomPortal } from "~~/components/CustomPortal/CustomPortal"; import { MetaHeader } from "~~/components/MetaHeader"; import { BundlingProcess } from "~~/components/Processes/BundlingProcess/BundlingProcess"; import { ConnectionProcess } from "~~/components/Processes/ConnectionProcess/ConnectionProcess"; import { RecoveryProcess } from "~~/components/Processes/RecoveryProcess/RecoveryProcess"; import { useRecoveryProcess } from "~~/hooks/flashbotRecoveryBundle/useRecoveryProcess"; +import { useShowError } from "~~/hooks/flashbotRecoveryBundle/useShowError"; +import ErrorSvg from "~~/public/assets/flashbotRecovery/error.svg"; import { RecoveryTx } from "~~/types/business"; import { BundlingSteps, RecoveryProcessStatus } from "~~/types/enums"; @@ -21,6 +24,7 @@ const Home: NextPage = () => { const [totalGasEstimate, setTotalGasEstimate] = useState(BigNumber.from("0")); const [isOnBasket, setIsOnBasket] = useState(false); const [currentBundleId, setCurrentBundleId] = useLocalStorage("bundleUuid", ""); + const { error, resetError } = useShowError(); const { data: processStatus, startRecoveryProcess, signRecoveryTransactions, blockCountdown } = useRecoveryProcess(); @@ -104,13 +108,16 @@ const Home: NextPage = () => { hackedAddress={hackedAddress} /> - {/* */} + {error != "" ? ( + resetError()} + title={"Something wrong has happend"} + description={error} + image={ErrorSvg} + /> + ) : ( + <> + )} {/* + + +