From 9276d74c71c425839aea44bf0aef2616374ece07 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 08:54:14 +0300 Subject: [PATCH 01/17] Add save abi and load abi funcs to abi.ts --- packages/nextjs/utils/abi.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/utils/abi.ts b/packages/nextjs/utils/abi.ts index 77ab6f0b..5fa21daf 100644 --- a/packages/nextjs/utils/abi.ts +++ b/packages/nextjs/utils/abi.ts @@ -1,5 +1,27 @@ import { isZeroAddress } from "./scaffold-eth/common"; -import { Address, Chain } from "viem"; +import { Abi, Address, Chain } from "viem"; + +const ABI_STORAGE_PREFIX = "abi_ninja_saved_abi_"; + +export const saveAbiToLocalStorage = (contractAddress: string, abi: Abi) => { + if (typeof window === "undefined") return; + try { + localStorage.setItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`, JSON.stringify(abi)); + } catch (error) { + console.error("Failed to save ABI to localStorage:", error); + } +}; + +export const getAbiFromLocalStorage = (contractAddress: string): Abi | null => { + if (typeof window === "undefined") return null; + try { + const savedAbi = localStorage.getItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); + return savedAbi ? JSON.parse(savedAbi) : null; + } catch (error) { + console.error("Failed to get ABI from localStorage:", error); + return null; + } +}; export const fetchContractABIFromEtherscan = async (verifiedContractAddress: Address, chainId: number) => { const apiKey = process.env.NEXT_PUBLIC_ETHERSCAN_V2_API_KEY; From 28602c2ee5380ba983699d8c9833576393c50029 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 09:10:27 +0300 Subject: [PATCH 02/17] Use abi from localstorage on homepage if the network is 31337 --- packages/nextjs/pages/index.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 359961c0..a76d102d 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -15,7 +15,7 @@ import { AddressInput } from "~~/components/scaffold-eth"; import useFetchContractAbi from "~~/hooks/useFetchContractAbi"; import { useHeimdall } from "~~/hooks/useHeimdall"; import { useGlobalState } from "~~/services/store/store"; -import { parseAndCorrectJSON } from "~~/utils/abi"; +import { getAbiFromLocalStorage, parseAndCorrectJSON, saveAbiToLocalStorage } from "~~/utils/abi"; import { notification } from "~~/utils/scaffold-eth"; enum TabName { @@ -88,6 +88,19 @@ const Home: NextPage = () => { } if (network === "31337" && isAddress(verifiedContractAddress)) { + try { + const savedAbi = getAbiFromLocalStorage(verifiedContractAddress); + if (savedAbi) { + setContractAbi(savedAbi); + setAbiContractAddress(verifiedContractAddress); + router.push(`/${verifiedContractAddress}/${network}`); + notification.success("Using previously saved ABI from local storage."); + } + setLocalAbiContractAddress(verifiedContractAddress); + return; + } catch (error) { + console.error("Error getting ABI from local storage:", error); + } setActiveTab(TabName.addressAbi); setLocalAbiContractAddress(verifiedContractAddress); return; @@ -105,6 +118,8 @@ const Home: NextPage = () => { handleFetchError, setContractAbi, setImplementationAddress, + router, + setAbiContractAddress, ]); useEffect(() => { @@ -128,6 +143,7 @@ const Home: NextPage = () => { try { const parsedAbi = parseAndCorrectJSON(localContractAbi); setContractAbi(parsedAbi); + saveAbiToLocalStorage(localAbiContractAddress, parsedAbi); router.push(`/${localAbiContractAddress}/${network}`); notification.success("ABI successfully loaded."); } catch (error) { From 4d093b241caca330709199bbc3680e0d971ada7b Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 09:27:34 +0300 Subject: [PATCH 03/17] Able to reload page on localhost --- .../pages/[contractAddress]/[network].tsx | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index adbf25dd..84e65997 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -12,7 +12,7 @@ import { SwitchTheme } from "~~/components/SwitchTheme"; import { ContractUI } from "~~/components/scaffold-eth"; import useFetchContractAbi from "~~/hooks/useFetchContractAbi"; import { useGlobalState } from "~~/services/store/store"; -import { getNetworkName, parseAndCorrectJSON } from "~~/utils/abi"; +import { getAbiFromLocalStorage, getNetworkName, parseAndCorrectJSON, saveAbiToLocalStorage } from "~~/utils/abi"; import { notification } from "~~/utils/scaffold-eth"; interface ParsedQueryContractDetailsPage extends ParsedUrlQuery { @@ -51,6 +51,8 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) const [localContractAbi, setLocalContractAbi] = useState(""); const [isUseLocalAbi, setIsUseLocalAbi] = useState(false); const [localContractData, setLocalContractData] = useState(null); + const [isLoadingLocalStorage, setIsLoadingLocalStorage] = useState(true); + const [savedAbiData, setSavedAbiData] = useState(null); const { chainId, setImplementationAddress, contractAbi, chains, addChain, setTargetNetwork } = useGlobalState( state => ({ @@ -63,25 +65,36 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) }), ); + useEffect(() => { + const savedAbi = getAbiFromLocalStorage(contractAddress); + setSavedAbiData(savedAbi); + setIsLoadingLocalStorage(false); + }, [contractAddress]); + + const shouldFetchFromEtherscan = !savedAbiData && contractAbi.length === 0; + const { contractData: fetchedContractData, error: fetchError, - isLoading, + isLoading: isLoadingEtherscan, implementationAddress, } = useFetchContractAbi({ contractAddress, chainId: parseInt(network), - disabled: contractAbi.length > 0, + disabled: !shouldFetchFromEtherscan, }); const effectiveContractData = contractAbi.length > 0 ? { abi: contractAbi, address: contractAddress } + : savedAbiData + ? { abi: savedAbiData, address: contractAddress } : isUseLocalAbi && localContractData ? localContractData : fetchedContractData; - const error = isUseLocalAbi ? null : fetchError; + const error = !effectiveContractData || !effectiveContractData.abi ? fetchError : null; + const isLoading = isLoadingLocalStorage || (isLoadingEtherscan && !savedAbiData && !contractAbi.length); useEffect(() => { if (network) { @@ -94,7 +107,19 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) if (implementationAddress) { setImplementationAddress(implementationAddress); } - }, [network, implementationAddress, chains, setTargetNetwork, setImplementationAddress]); + + if (fetchedContractData?.abi) { + saveAbiToLocalStorage(contractAddress, fetchedContractData.abi); + } + }, [ + network, + implementationAddress, + chains, + setTargetNetwork, + setImplementationAddress, + fetchedContractData, + contractAddress, + ]); const handleUserProvidedAbi = () => { try { @@ -132,7 +157,7 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps)
- {isLoading && !isUseLocalAbi ? ( + {isLoading ? (
From a1259f534e4258ef7e1b2522990a8024f7ada24d Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 09:51:41 +0300 Subject: [PATCH 04/17] Add remove abi from local storage function to abi.ts --- packages/nextjs/utils/abi.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/nextjs/utils/abi.ts b/packages/nextjs/utils/abi.ts index 5fa21daf..cf4a6958 100644 --- a/packages/nextjs/utils/abi.ts +++ b/packages/nextjs/utils/abi.ts @@ -83,3 +83,12 @@ export const getNetworkName = (chains: Chain[], chainId: number) => { const chain = chains.find(chain => chain.id === chainId); return chain ? chain.name : "Unknown Network"; }; + +export const removeAbiFromLocalStorage = (contractAddress: string) => { + if (typeof window === "undefined") return; + try { + localStorage.removeItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); + } catch (error) { + console.error("Failed to remove ABI from localStorage:", error); + } +}; From 03b1ccafeca2f4a57f5aecd92969bf1287da7cd3 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 10:01:23 +0300 Subject: [PATCH 05/17] Able to remove abi from localstorage --- .../pages/[contractAddress]/[network].tsx | 46 +++++++++++++++++-- packages/nextjs/pages/index.tsx | 4 +- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 84e65997..87d9e1e8 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { GetServerSideProps } from "next"; import { ParsedUrlQuery } from "querystring"; import { Abi, Address, isAddress } from "viem"; -import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { ExclamationTriangleIcon, TrashIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniHeader } from "~~/components/MiniHeader"; import { formDataToChain, storeChainInLocalStorage } from "~~/components/NetworksDropdown/utils"; @@ -12,7 +12,13 @@ import { SwitchTheme } from "~~/components/SwitchTheme"; import { ContractUI } from "~~/components/scaffold-eth"; import useFetchContractAbi from "~~/hooks/useFetchContractAbi"; import { useGlobalState } from "~~/services/store/store"; -import { getAbiFromLocalStorage, getNetworkName, parseAndCorrectJSON, saveAbiToLocalStorage } from "~~/utils/abi"; +import { + getAbiFromLocalStorage, + getNetworkName, + parseAndCorrectJSON, + removeAbiFromLocalStorage, + saveAbiToLocalStorage, +} from "~~/utils/abi"; import { notification } from "~~/utils/scaffold-eth"; interface ParsedQueryContractDetailsPage extends ParsedUrlQuery { @@ -69,7 +75,12 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) const savedAbi = getAbiFromLocalStorage(contractAddress); setSavedAbiData(savedAbi); setIsLoadingLocalStorage(false); - }, [contractAddress]); + + if (contractAbi.length > 0) { + saveAbiToLocalStorage(contractAddress, contractAbi); + setSavedAbiData(contractAbi); + } + }, [contractAddress, contractAbi]); const shouldFetchFromEtherscan = !savedAbiData && contractAbi.length === 0; @@ -125,6 +136,8 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) try { const parsedAbi = parseAndCorrectJSON(localContractAbi); if (parsedAbi) { + saveAbiToLocalStorage(contractAddress, parsedAbi); + setSavedAbiData(parsedAbi); setIsUseLocalAbi(true); setLocalContractData({ abi: parsedAbi, address: contractAddress }); notification.success("ABI successfully loaded."); @@ -151,6 +164,17 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) notification.success("Custom chain successfully loaded."); }; + const isUsingLocalStorage = Boolean(savedAbiData) && (!contractAbi.length || contractAbi === savedAbiData); + + const handleRemoveSavedAbi = () => { + removeAbiFromLocalStorage(contractAddress); + setSavedAbiData(null); + if (contractAbi.length > 0) { + router.reload(); + } + notification.success("Saved ABI removed successfully."); + }; + return ( <> @@ -162,7 +186,21 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps)
) : effectiveContractData && effectiveContractData?.abi?.length > 0 ? ( - + <> + {isUsingLocalStorage && ( +
+ +
+ )} + + ) : (
diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index a76d102d..07266d5e 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -96,14 +96,12 @@ const Home: NextPage = () => { router.push(`/${verifiedContractAddress}/${network}`); notification.success("Using previously saved ABI from local storage."); } + setActiveTab(TabName.addressAbi); setLocalAbiContractAddress(verifiedContractAddress); return; } catch (error) { console.error("Error getting ABI from local storage:", error); } - setActiveTab(TabName.addressAbi); - setLocalAbiContractAddress(verifiedContractAddress); - return; } if (error && isAddress(verifiedContractAddress)) { From b9a57e44fe98225a0de403fefed1126cda3224a9 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:27:55 +0300 Subject: [PATCH 06/17] Save abi for all contracts --- packages/nextjs/pages/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 07266d5e..84390d60 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -85,22 +85,26 @@ const Home: NextPage = () => { if (contractData?.abi) { setContractAbi(contractData.abi); + saveAbiToLocalStorage(verifiedContractAddress, contractData.abi); } - if (network === "31337" && isAddress(verifiedContractAddress)) { + if (isAddress(verifiedContractAddress)) { try { const savedAbi = getAbiFromLocalStorage(verifiedContractAddress); if (savedAbi) { setContractAbi(savedAbi); setAbiContractAddress(verifiedContractAddress); router.push(`/${verifiedContractAddress}/${network}`); - notification.success("Using previously saved ABI from local storage."); + return; } + } catch (error) { + console.error("Error getting ABI from local storage:", error); + } + + if (network === "31337") { setActiveTab(TabName.addressAbi); setLocalAbiContractAddress(verifiedContractAddress); return; - } catch (error) { - console.error("Error getting ABI from local storage:", error); } } From 1e667e96568013a49bba175389b9ed32e4b38b93 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:30:07 +0300 Subject: [PATCH 07/17] Add modal to remove the abi --- packages/nextjs/components/MiniHeader.tsx | 74 ++++++++++++++++++- .../pages/[contractAddress]/[network].tsx | 67 ++++++----------- packages/nextjs/services/store/store.ts | 4 + 3 files changed, 99 insertions(+), 46 deletions(-) diff --git a/packages/nextjs/components/MiniHeader.tsx b/packages/nextjs/components/MiniHeader.tsx index 9dc9f769..d39de816 100644 --- a/packages/nextjs/components/MiniHeader.tsx +++ b/packages/nextjs/components/MiniHeader.tsx @@ -1,9 +1,26 @@ +import { useState } from "react"; import Image from "next/image"; import Link from "next/link"; -import { Bars3Icon } from "@heroicons/react/24/outline"; +import { Bars3Icon, Cog6ToothIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; +import { useGlobalState } from "~~/services/store/store"; +import { removeAbiFromLocalStorage } from "~~/utils/abi"; export const MiniHeader = () => { + const [showSuccess, setShowSuccess] = useState(false); + const { savedAbiData, setSavedAbiData, contractAddress } = useGlobalState(state => ({ + savedAbiData: state.savedAbiData, + setSavedAbiData: state.setSavedAbiData, + contractAddress: state.abiContractAddress, + })); + + const handleRemoveSavedAbi = () => { + removeAbiFromLocalStorage(contractAddress); + setSavedAbiData(null); + setShowSuccess(true); + setTimeout(() => setShowSuccess(false), 3000); + }; + return (
@@ -20,6 +37,61 @@ export const MiniHeader = () => {
+ + +
+
+

Configuration

+
+ +
+
+ +
+
+

Local Storage

+
+

+ 🥷abi.ninja saves the ABI to the browser's local storage when you search for a contract for the + first time. If you have an abi saved for the current contract, you will be able to remove it by + clicking the button below. +

+

If you're interacting with a verified contract. This setting does not matter.

+

If you're not sure what this is, you can safely ignore this.

+
+
+ + {showSuccess && ( +
+ ABI removed successfully! Please reload the page to see the changes. +
+ )} + + {savedAbiData && ( +
+ +
+ )} +
+
+
diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 87d9e1e8..afc9e031 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { GetServerSideProps } from "next"; import { ParsedUrlQuery } from "querystring"; import { Abi, Address, isAddress } from "viem"; -import { ExclamationTriangleIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniHeader } from "~~/components/MiniHeader"; import { formDataToChain, storeChainInLocalStorage } from "~~/components/NetworksDropdown/utils"; @@ -12,13 +12,7 @@ import { SwitchTheme } from "~~/components/SwitchTheme"; import { ContractUI } from "~~/components/scaffold-eth"; import useFetchContractAbi from "~~/hooks/useFetchContractAbi"; import { useGlobalState } from "~~/services/store/store"; -import { - getAbiFromLocalStorage, - getNetworkName, - parseAndCorrectJSON, - removeAbiFromLocalStorage, - saveAbiToLocalStorage, -} from "~~/utils/abi"; +import { getAbiFromLocalStorage, getNetworkName, parseAndCorrectJSON, saveAbiToLocalStorage } from "~~/utils/abi"; import { notification } from "~~/utils/scaffold-eth"; interface ParsedQueryContractDetailsPage extends ParsedUrlQuery { @@ -58,18 +52,26 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) const [isUseLocalAbi, setIsUseLocalAbi] = useState(false); const [localContractData, setLocalContractData] = useState(null); const [isLoadingLocalStorage, setIsLoadingLocalStorage] = useState(true); - const [savedAbiData, setSavedAbiData] = useState(null); - const { chainId, setImplementationAddress, contractAbi, chains, addChain, setTargetNetwork } = useGlobalState( - state => ({ - chains: state.chains, - addChain: state.addChain, - chainId: state.targetNetwork.id, - setTargetNetwork: state.setTargetNetwork, - setImplementationAddress: state.setImplementationAddress, - contractAbi: state.contractAbi, - }), - ); + const { + chainId, + setImplementationAddress, + contractAbi, + chains, + addChain, + setTargetNetwork, + savedAbiData, + setSavedAbiData, + } = useGlobalState(state => ({ + chains: state.chains, + addChain: state.addChain, + chainId: state.targetNetwork.id, + setTargetNetwork: state.setTargetNetwork, + setImplementationAddress: state.setImplementationAddress, + contractAbi: state.contractAbi, + savedAbiData: state.savedAbiData, + setSavedAbiData: state.setSavedAbiData, + })); useEffect(() => { const savedAbi = getAbiFromLocalStorage(contractAddress); @@ -164,17 +166,6 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) notification.success("Custom chain successfully loaded."); }; - const isUsingLocalStorage = Boolean(savedAbiData) && (!contractAbi.length || contractAbi === savedAbiData); - - const handleRemoveSavedAbi = () => { - removeAbiFromLocalStorage(contractAddress); - setSavedAbiData(null); - if (contractAbi.length > 0) { - router.reload(); - } - notification.success("Saved ABI removed successfully."); - }; - return ( <> @@ -186,21 +177,7 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps)
) : effectiveContractData && effectiveContractData?.abi?.length > 0 ? ( - <> - {isUsingLocalStorage && ( -
- -
- )} - - + ) : (
diff --git a/packages/nextjs/services/store/store.ts b/packages/nextjs/services/store/store.ts index f2d2bbbf..ca761cfc 100644 --- a/packages/nextjs/services/store/store.ts +++ b/packages/nextjs/services/store/store.ts @@ -22,6 +22,8 @@ type GlobalState = { setAbiContractAddress: (newAbiContractAddress: Address | "") => void; implementationAddress: Address | ""; setImplementationAddress: (newImplementationAddress: Address | "") => void; + savedAbiData: Abi | null; + setSavedAbiData: (newAbiData: Abi | null) => void; }; export const useGlobalState = create(set => ({ @@ -63,4 +65,6 @@ export const useGlobalState = create(set => ({ setAbiContractAddress: (newAddress: Address | ""): void => set({ abiContractAddress: newAddress }), implementationAddress: "", setImplementationAddress: (newAddress: Address | ""): void => set({ implementationAddress: newAddress }), + savedAbiData: null, + setSavedAbiData: (newAbiData: Abi | null): void => set({ savedAbiData: newAbiData }), })); From 8656bbcf49272b02d298917291a9311d0b370fd0 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:35:40 +0300 Subject: [PATCH 08/17] Use better X to close the modal --- packages/nextjs/components/MiniHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/components/MiniHeader.tsx b/packages/nextjs/components/MiniHeader.tsx index d39de816..83268be2 100644 --- a/packages/nextjs/components/MiniHeader.tsx +++ b/packages/nextjs/components/MiniHeader.tsx @@ -51,7 +51,7 @@ export const MiniHeader = () => {

Configuration

-
From 3bea97512aed7b217be82a387c6ae3e0ff10947a Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 08:55:33 +0300 Subject: [PATCH 09/17] Remove one eslint warning(exhaustive deps) --- packages/nextjs/pages/[contractAddress]/[network].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index afc9e031..adbfc01e 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -82,7 +82,7 @@ const ContractDetailPage = ({ addressFromUrl, chainIdFromUrl }: ServerSideProps) saveAbiToLocalStorage(contractAddress, contractAbi); setSavedAbiData(contractAbi); } - }, [contractAddress, contractAbi]); + }, [contractAddress, contractAbi, setSavedAbiData]); const shouldFetchFromEtherscan = !savedAbiData && contractAbi.length === 0; From e1b6adc86d08176e321e0496db914121feb71fe9 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 09:17:05 +0300 Subject: [PATCH 10/17] Change wording in Configuration modal --- packages/nextjs/components/MiniHeader.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/nextjs/components/MiniHeader.tsx b/packages/nextjs/components/MiniHeader.tsx index 83268be2..4b9fef1f 100644 --- a/packages/nextjs/components/MiniHeader.tsx +++ b/packages/nextjs/components/MiniHeader.tsx @@ -60,14 +60,13 @@ export const MiniHeader = () => {

Local Storage

-
+

🥷abi.ninja saves the ABI to the browser's local storage when you search for a contract for the first time. If you have an abi saved for the current contract, you will be able to remove it by clicking the button below.

-

If you're interacting with a verified contract. This setting does not matter.

-

If you're not sure what this is, you can safely ignore this.

+

You can safely ignore this if you're interacting with a verified contract.

From 496c763c81df380bd9af4b45397c3a73ee4a3ee6 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 09:18:48 +0300 Subject: [PATCH 11/17] Reorder functions in abi.ts --- packages/nextjs/utils/abi.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/nextjs/utils/abi.ts b/packages/nextjs/utils/abi.ts index cf4a6958..846f13dd 100644 --- a/packages/nextjs/utils/abi.ts +++ b/packages/nextjs/utils/abi.ts @@ -3,6 +3,17 @@ import { Abi, Address, Chain } from "viem"; const ABI_STORAGE_PREFIX = "abi_ninja_saved_abi_"; +export const getAbiFromLocalStorage = (contractAddress: string): Abi | null => { + if (typeof window === "undefined") return null; + try { + const savedAbi = localStorage.getItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); + return savedAbi ? JSON.parse(savedAbi) : null; + } catch (error) { + console.error("Failed to get ABI from localStorage:", error); + return null; + } +}; + export const saveAbiToLocalStorage = (contractAddress: string, abi: Abi) => { if (typeof window === "undefined") return; try { @@ -12,14 +23,12 @@ export const saveAbiToLocalStorage = (contractAddress: string, abi: Abi) => { } }; -export const getAbiFromLocalStorage = (contractAddress: string): Abi | null => { - if (typeof window === "undefined") return null; +export const removeAbiFromLocalStorage = (contractAddress: string) => { + if (typeof window === "undefined") return; try { - const savedAbi = localStorage.getItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); - return savedAbi ? JSON.parse(savedAbi) : null; + localStorage.removeItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); } catch (error) { - console.error("Failed to get ABI from localStorage:", error); - return null; + console.error("Failed to remove ABI from localStorage:", error); } }; @@ -83,12 +92,3 @@ export const getNetworkName = (chains: Chain[], chainId: number) => { const chain = chains.find(chain => chain.id === chainId); return chain ? chain.name : "Unknown Network"; }; - -export const removeAbiFromLocalStorage = (contractAddress: string) => { - if (typeof window === "undefined") return; - try { - localStorage.removeItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); - } catch (error) { - console.error("Failed to remove ABI from localStorage:", error); - } -}; From 010f42c7550e478c1c16a90d8378074f5e391cc3 Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:11:31 +0300 Subject: [PATCH 12/17] Store all contracts under one local storage key --- packages/nextjs/utils/abi.ts | 39 +++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/utils/abi.ts b/packages/nextjs/utils/abi.ts index 846f13dd..3cc0c3c8 100644 --- a/packages/nextjs/utils/abi.ts +++ b/packages/nextjs/utils/abi.ts @@ -1,13 +1,38 @@ import { isZeroAddress } from "./scaffold-eth/common"; import { Abi, Address, Chain } from "viem"; -const ABI_STORAGE_PREFIX = "abi_ninja_saved_abi_"; +const ABI_STORAGE_KEY = "abi_ninja_storage"; + +interface AbiStorage { + version: string; + abis: Record; +} + +const getAbiStorage = (): AbiStorage => { + if (typeof window === "undefined") return { version: "1.0", abis: {} }; + try { + const storage = localStorage.getItem(ABI_STORAGE_KEY); + return storage ? JSON.parse(storage) : { version: "1.0", abis: {} }; + } catch (error) { + console.error("Failed to get ABI storage:", error); + return { version: "1.0", abis: {} }; + } +}; + +const saveAbiStorage = (storage: AbiStorage) => { + if (typeof window === "undefined") return; + try { + localStorage.setItem(ABI_STORAGE_KEY, JSON.stringify(storage)); + } catch (error) { + console.error("Failed to save ABI storage:", error); + } +}; export const getAbiFromLocalStorage = (contractAddress: string): Abi | null => { if (typeof window === "undefined") return null; try { - const savedAbi = localStorage.getItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); - return savedAbi ? JSON.parse(savedAbi) : null; + const storage = getAbiStorage(); + return storage.abis[contractAddress.toLowerCase()] || null; } catch (error) { console.error("Failed to get ABI from localStorage:", error); return null; @@ -17,7 +42,9 @@ export const getAbiFromLocalStorage = (contractAddress: string): Abi | null => { export const saveAbiToLocalStorage = (contractAddress: string, abi: Abi) => { if (typeof window === "undefined") return; try { - localStorage.setItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`, JSON.stringify(abi)); + const storage = getAbiStorage(); + storage.abis[contractAddress.toLowerCase()] = abi; + saveAbiStorage(storage); } catch (error) { console.error("Failed to save ABI to localStorage:", error); } @@ -26,7 +53,9 @@ export const saveAbiToLocalStorage = (contractAddress: string, abi: Abi) => { export const removeAbiFromLocalStorage = (contractAddress: string) => { if (typeof window === "undefined") return; try { - localStorage.removeItem(`${ABI_STORAGE_PREFIX}${contractAddress.toLowerCase()}`); + const storage = getAbiStorage(); + delete storage.abis[contractAddress.toLowerCase()]; + saveAbiStorage(storage); } catch (error) { console.error("Failed to remove ABI from localStorage:", error); } From 95101ca9811268e4e9e4d73aed686d1352afe1aa Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:22:29 +0300 Subject: [PATCH 13/17] Use type instead of interface for AbiStorage in abi.ts --- packages/nextjs/utils/abi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/utils/abi.ts b/packages/nextjs/utils/abi.ts index 3cc0c3c8..8a8da8e5 100644 --- a/packages/nextjs/utils/abi.ts +++ b/packages/nextjs/utils/abi.ts @@ -3,10 +3,10 @@ import { Abi, Address, Chain } from "viem"; const ABI_STORAGE_KEY = "abi_ninja_storage"; -interface AbiStorage { +type AbiStorage = { version: string; abis: Record; -} +}; const getAbiStorage = (): AbiStorage => { if (typeof window === "undefined") return { version: "1.0", abis: {} }; From 1c42b17fdf8512ec131fa796817bda712b22215f Mon Sep 17 00:00:00 2001 From: portdeveloper <108868128+portdeveloper@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:54:32 +0300 Subject: [PATCH 14/17] Save abi only for localhost + show the saved abi in Configuration modal --- packages/nextjs/components/MiniHeader.tsx | 87 ++++++++++++++----- .../pages/[contractAddress]/[network].tsx | 85 +++++++----------- packages/nextjs/pages/index.tsx | 30 +++---- packages/nextjs/services/store/store.ts | 4 - 4 files changed, 106 insertions(+), 100 deletions(-) diff --git a/packages/nextjs/components/MiniHeader.tsx b/packages/nextjs/components/MiniHeader.tsx index 4b9fef1f..7f4eb63f 100644 --- a/packages/nextjs/components/MiniHeader.tsx +++ b/packages/nextjs/components/MiniHeader.tsx @@ -1,26 +1,49 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { Bars3Icon, Cog6ToothIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; import { useGlobalState } from "~~/services/store/store"; -import { removeAbiFromLocalStorage } from "~~/utils/abi"; +import { getAbiFromLocalStorage, removeAbiFromLocalStorage, saveAbiToLocalStorage } from "~~/utils/abi"; +import { parseAndCorrectJSON } from "~~/utils/abi"; +import { notification } from "~~/utils/scaffold-eth"; export const MiniHeader = () => { const [showSuccess, setShowSuccess] = useState(false); - const { savedAbiData, setSavedAbiData, contractAddress } = useGlobalState(state => ({ - savedAbiData: state.savedAbiData, - setSavedAbiData: state.setSavedAbiData, + const [editedAbi, setEditedAbi] = useState(""); + const { contractAddress } = useGlobalState(state => ({ contractAddress: state.abiContractAddress, })); + const savedAbi = getAbiFromLocalStorage(contractAddress); + const formattedAbi = savedAbi ? JSON.stringify(savedAbi, null, 2) : ""; + + useEffect(() => { + setEditedAbi(formattedAbi); + }, [formattedAbi]); + const handleRemoveSavedAbi = () => { removeAbiFromLocalStorage(contractAddress); - setSavedAbiData(null); setShowSuccess(true); setTimeout(() => setShowSuccess(false), 3000); }; + const handleSaveEdit = () => { + try { + const parsedAbi = parseAndCorrectJSON(editedAbi); + if (parsedAbi) { + saveAbiToLocalStorage(contractAddress, parsedAbi); + notification.success("ABI updated successfully!"); + } + } catch (error) { + notification.error("Invalid ABI format. Please ensure it is valid JSON."); + } + }; + + const handleResetAbi = () => { + setEditedAbi(formattedAbi); + }; + return (
@@ -62,30 +85,48 @@ export const MiniHeader = () => {

Local Storage

- 🥷abi.ninja saves the ABI to the browser's local storage when you search for a contract for the - first time. If you have an abi saved for the current contract, you will be able to remove it by - clicking the button below. + 🥷abi.ninja automatically saves the contract ABI to your browser's local storage when you first + interact with a contract on local networks (like Anvil or Hardhat). This speeds up future + interactions with the same contract. You can modify or remove the saved ABI below.

-

You can safely ignore this if you're interacting with a verified contract.

- {showSuccess && ( -
- ABI removed successfully! Please reload the page to see the changes. + {savedAbi && ( +
+

Saved ABI for {contractAddress}

+
+