From 0038c992617992b30a6266bf7c3b2a299794c818 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Wed, 21 Aug 2024 17:46:54 +0900 Subject: [PATCH] update home page --- packages/nextjs/hooks/useFetchContractAbi.ts | 25 +++- .../pages/[contractAddress]/[network].tsx | 2 +- packages/nextjs/pages/index.tsx | 115 ++++++++---------- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/packages/nextjs/hooks/useFetchContractAbi.ts b/packages/nextjs/hooks/useFetchContractAbi.ts index 17496ca5..8b216acb 100644 --- a/packages/nextjs/hooks/useFetchContractAbi.ts +++ b/packages/nextjs/hooks/useFetchContractAbi.ts @@ -1,14 +1,26 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan } from "~~/utils/abi"; +import { isAddress } from "viem"; +import { UsePublicClientReturnType } from "wagmi"; +import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan, parseAndCorrectJSON } from "~~/utils/abi"; import { detectProxyTarget } from "~~/utils/abi-ninja/proxyContracts"; const ANYABI_TIMEOUT = 3000; -const useFetchContractAbi = (contractAddress: string, parsedNetworkId: number, publicClient: any) => { +type FetchContractAbiParams = { + contractAddress: string; + chainId: number; + publicClient: UsePublicClientReturnType; +}; + +const useFetchContractAbi = ({ contractAddress, chainId, publicClient }: FetchContractAbiParams) => { const [implementationAddress, setImplementationAddress] = useState(null); const fetchAbi = async () => { + if (!isAddress(contractAddress)) { + throw new Error("Invalid contract address"); + } + try { const implAddress = await detectProxyTarget(contractAddress, publicClient); if (implAddress) { @@ -23,7 +35,7 @@ const useFetchContractAbi = (contractAddress: string, parsedNetworkId: number, p }); // Race between the AnyABI fetch and the timeout - const abi = await Promise.race([fetchContractABIFromAnyABI(addressToUse, parsedNetworkId), timeoutPromise]); + const abi = await Promise.race([fetchContractABIFromAnyABI(addressToUse, chainId), timeoutPromise]); if (!abi) throw new Error("Got empty or undefined ABI from AnyABI"); @@ -32,15 +44,16 @@ const useFetchContractAbi = (contractAddress: string, parsedNetworkId: number, p console.error("Error or timeout fetching ABI from AnyABI: ", error); console.log("Falling back to Etherscan..."); - const abiString = await fetchContractABIFromEtherscan(contractAddress, parsedNetworkId); - const parsedAbi = JSON.parse(abiString); + const abiString = await fetchContractABIFromEtherscan(contractAddress, chainId); + const parsedAbi = parseAndCorrectJSON(abiString); return { abi: parsedAbi, address: contractAddress }; } }; const { data, error, isLoading } = useQuery({ - queryKey: ["contractAbi", { contractAddress, chainId: parsedNetworkId }], + queryKey: ["contractAbi", { contractAddress, chainId: chainId }], queryFn: fetchAbi, + enabled: isAddress(contractAddress) && chainId !== 31337, }); return { diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 06c889c3..20365c62 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -86,7 +86,7 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) error: fetchError, isLoading, implementationAddress, - } = useFetchContractAbi(contractAddress, parseInt(network), publicClient); + } = useFetchContractAbi({ contractAddress, chainId: parseInt(network), publicClient }); const effectiveContractData = isUseLocalAbi && contractData ? contractData : fetchedContractData; diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index e42c6058..d591d8b6 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -12,9 +12,9 @@ import { MiniFooter } from "~~/components/MiniFooter"; import { NetworksDropdown } from "~~/components/NetworksDropdown/NetworksDropdown"; import { SwitchTheme } from "~~/components/SwitchTheme"; import { AddressInput } from "~~/components/scaffold-eth"; +import useFetchContractAbi from "~~/hooks/useFetchContractAbi"; import { useAbiNinjaState } from "~~/services/store/store"; -import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan, parseAndCorrectJSON } from "~~/utils/abi"; -import { detectProxyTarget } from "~~/utils/abi-ninja/proxyContracts"; +import { parseAndCorrectJSON } from "~~/utils/abi"; import { notification } from "~~/utils/scaffold-eth"; enum TabName { @@ -30,7 +30,6 @@ const Home: NextPage = () => { const [verifiedContractAddress, setVerifiedContractAddress] = useState(""); const [localAbiContractAddress, setLocalAbiContractAddress] = useState(""); const [localContractAbi, setLocalContractAbi] = useState(""); - const [isFetchingAbi, setIsFetchingAbi] = useState(false); const publicClient = usePublicClient({ chainId: parseInt(network), @@ -42,66 +41,64 @@ const Home: NextPage = () => { setImplementationAddress: state.setImplementationAddress, })); - const [isAbiAvailable, setIsAbiAvailable] = useState(false); - const router = useRouter(); - useEffect(() => { - const fetchContractAbi = async () => { - setIsFetchingAbi(true); - try { - const implementationAddress = await detectProxyTarget(verifiedContractAddress as Address, publicClient); - - if (implementationAddress) { - setImplementationAddress(implementationAddress); - } - const abi = await fetchContractABIFromAnyABI( - implementationAddress || verifiedContractAddress, - parseInt(network), - ); - if (!abi) throw new Error("Got empty or undefined ABI from AnyABI"); - setContractAbi(abi); - setIsAbiAvailable(true); - } catch (error) { - console.error("Error fetching ABI from AnyABI: ", error); - console.log("Trying to fetch ABI from Etherscan..."); - try { - const abiString = await fetchContractABIFromEtherscan(verifiedContractAddress, parseInt(network)); - const abi = JSON.parse(abiString); - setContractAbi(abi); - setIsAbiAvailable(true); - } catch (etherscanError: any) { - setIsAbiAvailable(false); - console.error("Error fetching ABI from Etherscan: ", etherscanError); + const { + contractData, + error, + isLoading: isFetchingAbi, + implementationAddress, + } = useFetchContractAbi({ contractAddress: verifiedContractAddress, chainId: parseInt(network), publicClient }); - const bytecode = await publicClient?.getBytecode({ - address: verifiedContractAddress as Address, - }); - const isContract = Boolean(bytecode) && bytecode !== "0x"; + const isAbiAvailable = contractData?.abi && contractData.abi.length > 0; - if (isContract) { - setLocalAbiContractAddress(verifiedContractAddress); - setActiveTab(TabName.addressAbi); - } else { - notification.error("Address is not a contract, are you sure you are on the correct chain?"); - } - } - } finally { - setIsFetchingAbi(false); - } - }; + const handleFetchError = useCallback(async () => { + try { + const bytecode = await publicClient?.getBytecode({ + address: verifiedContractAddress as Address, + }); + const isContract = Boolean(bytecode) && bytecode !== "0x"; - if (isAddress(verifiedContractAddress)) { - if (network === "31337") { - setActiveTab(TabName.addressAbi); + if (isContract) { setLocalAbiContractAddress(verifiedContractAddress); - return; + setActiveTab(TabName.addressAbi); + } else { + notification.error("Address is not a contract, are you sure you are on the correct chain?"); } - fetchContractAbi(); - } else { - setIsAbiAvailable(false); + } catch (error) { + console.error("Error checking if address is a contract:", error); + notification.error("Error checking if address is a contract. Please try again."); + } + }, [publicClient, verifiedContractAddress, setLocalAbiContractAddress, setActiveTab]); + + useEffect(() => { + if (implementationAddress) { + setImplementationAddress(implementationAddress); + } + + if (contractData?.abi) { + setContractAbi(contractData.abi); + } + + if (network === "31337") { + setActiveTab(TabName.addressAbi); + setLocalAbiContractAddress(verifiedContractAddress); + return; + } + + if (error && isAddress(verifiedContractAddress)) { + handleFetchError(); } - }, [verifiedContractAddress, network, setContractAbi, publicClient, setImplementationAddress]); + }, [ + contractData, + error, + implementationAddress, + network, + verifiedContractAddress, + handleFetchError, + setContractAbi, + setImplementationAddress, + ]); useEffect(() => { if (router.pathname === "/") { @@ -132,7 +129,6 @@ const Home: NextPage = () => { }; const fetchAbiFromHeimdall = async (contractAddress: Address) => { - setIsFetchingAbi(true); try { const rpcUrlWithoutHttps = publicClient?.chain.rpcUrls.default.http[0].substring(8); const response = await fetch( @@ -141,19 +137,14 @@ const Home: NextPage = () => { const abi = await response.json(); if (abi.length === 0) { notification.error("Failed to fetch ABI from Heimdall. Please try again or enter ABI manually."); - setIsFetchingAbi(false); return; } setContractAbi(abi); - setIsAbiAvailable(true); setAbiContractAddress(contractAddress); router.push(`/${contractAddress}/${network}`); } catch (error) { console.error("Error fetching ABI from Heimdall: ", error); notification.error("Failed to fetch ABI from Heimdall. Please try again or enter ABI manually."); - setIsAbiAvailable(false); - } finally { - setIsFetchingAbi(false); } };