From 0f5dafca8b1ae61f89d401cc98105d99cd803ee7 Mon Sep 17 00:00:00 2001 From: "Shiv Bhonde | shivbhonde.eth" Date: Mon, 1 Apr 2024 22:16:19 +0530 Subject: [PATCH 1/5] Allow abi.ninja to iframe into any app as safe app (#76) --- packages/nextjs/services/web3/wagmiConnectors.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/services/web3/wagmiConnectors.tsx b/packages/nextjs/services/web3/wagmiConnectors.tsx index 6bdac2b8..1d42e30d 100644 --- a/packages/nextjs/services/web3/wagmiConnectors.tsx +++ b/packages/nextjs/services/web3/wagmiConnectors.tsx @@ -62,7 +62,7 @@ const wallets = [ }), ] : []), - safeWallet({ ...walletsOptions, debug: false, allowedDomains: [/gnosis-safe.io$/, /app.safe.global$/] }), + safeWallet({ ...walletsOptions }), ]; /** From b8b825752c7b99cb06da17052e11039c014aeb3c Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:52:45 +0300 Subject: [PATCH 2/5] Enable proxy contracts (#72) --- .../scaffold-eth/Contract/ContractUI.tsx | 15 ++++++++++--- packages/nextjs/package.json | 1 + .../pages/[contractAddress]/[network].tsx | 16 ++++++++++++-- packages/nextjs/pages/index.tsx | 22 ++++++++++++++++--- packages/nextjs/services/store/store.ts | 4 ++++ yarn.lock | 8 +++++++ 6 files changed, 58 insertions(+), 8 deletions(-) diff --git a/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx b/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx index 8e3ba6ef..73dc28a2 100644 --- a/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx +++ b/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx @@ -61,8 +61,11 @@ const mainNetworks = getTargetNetworks(); **/ export const ContractUI = ({ className = "", initialContractData }: ContractUIProps) => { const [refreshDisplayVariables, triggerRefreshDisplayVariables] = useReducer(value => !value, false); - const mainChainId = useAbiNinjaState(state => state.mainChainId); - const mainNetwork = mainNetworks.find(network => network.id === mainChainId); + const { implementationAddress, chainId } = useAbiNinjaState(state => ({ + chainId: state.mainChainId, + implementationAddress: state.implementationAddress, + })); + const mainNetwork = mainNetworks.find(network => network.id === chainId); const networkColor = useNetworkColor(mainNetwork); const router = useRouter(); const { network } = router.query as { network?: string }; @@ -123,7 +126,7 @@ export const ContractUI = ({ className = "", initialContractData }: ContractUIPr const { data: contractNameData, isLoading: isContractNameLoading } = useContractRead({ address: initialContractData.address, abi: initialContractData.abi, - chainId: mainChainId, + chainId: chainId, functionName: "name", }); @@ -197,6 +200,12 @@ export const ContractUI = ({ className = "", initialContractData }: ContractUIPr {displayContractName}
+ {implementationAddress && ( +
+ Implementation Address +
+
+ )}
Balance: diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 83609f16..362de183 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -21,6 +21,7 @@ "@uniswap/v2-sdk": "^3.0.1", "blo": "^1.0.1", "daisyui": "^4.4.19", + "evm-proxy-detection": "^1.2.0", "next": "13.3.4", "next-plausible": "^3.12.0", "nextjs-progressbar": "^0.0.16", diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index dd22bc4e..1aa382ed 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; +import { AlchemyProvider } from "@ethersproject/providers"; +import detectProxyTarget from "evm-proxy-detection"; import { ParsedUrlQuery } from "querystring"; import { Abi, isAddress } from "viem"; import * as chains from "viem/chains"; @@ -8,6 +10,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniHeader } from "~~/components/MiniHeader"; import { ContractUI } from "~~/components/scaffold-eth"; +import scaffoldConfig from "~~/scaffold.config"; import { useAbiNinjaState } from "~~/services/store/store"; import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan } from "~~/utils/abi"; @@ -32,10 +35,12 @@ const ContractDetailPage = () => { contractAbi: storedAbi, setMainChainId, chainId, + setImplementationAddress, } = useAbiNinjaState(state => ({ contractAbi: state.contractAbi, setMainChainId: state.setMainChainId, chainId: state.mainChainId, + setImplementationAddress: state.setImplementationAddress, })); const getNetworkName = (chainId: number) => { @@ -72,7 +77,14 @@ const ContractDetailPage = () => { } try { - const abi = await fetchContractABIFromAnyABI(contractAddress, parsedNetworkId); + const alchemyProvider = new AlchemyProvider(parseInt(network), scaffoldConfig.alchemyApiKey); + const requestFunc = ({ method, params }: { method: string; params: any }) => + alchemyProvider.send(method, params); + const implementationAddress = await detectProxyTarget(contractAddress, requestFunc); + if (implementationAddress) { + setImplementationAddress(implementationAddress); + } + const abi = await fetchContractABIFromAnyABI(implementationAddress || contractAddress, parsedNetworkId); if (!abi) throw new Error("Got empty or undefined ABI from AnyABI"); setContractData({ abi, address: contractAddress }); setError(null); @@ -102,7 +114,7 @@ const ContractDetailPage = () => { } } } - }, [contractAddress, network, storedAbi, setMainChainId]); + }, [contractAddress, network, storedAbi, setMainChainId, setImplementationAddress]); return ( <> diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 7e7fe631..1d5dc492 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; +import { AlchemyProvider } from "@ethersproject/providers"; +import detectProxyTarget from "evm-proxy-detection"; import type { NextPage } from "next"; import { Address, isAddress } from "viem"; import { usePublicClient } from "wagmi"; @@ -9,6 +11,7 @@ import { MetaHeader } from "~~/components/MetaHeader"; import { MiniFooter } from "~~/components/MiniFooter"; import { NetworksDropdown } from "~~/components/NetworksDropdown"; import { AddressInput, InputBase } from "~~/components/scaffold-eth"; +import scaffoldConfig from "~~/scaffold.config"; import { useAbiNinjaState } from "~~/services/store/store"; import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan, parseAndCorrectJSON } from "~~/utils/abi"; import { getTargetNetworks, notification } from "~~/utils/scaffold-eth"; @@ -32,13 +35,17 @@ const Home: NextPage = () => { const [isCheckingContractAddress, setIsCheckingContractAddress] = useState(false); const [isContract, setIsContract] = useState(false); + const alchemyProvider = new AlchemyProvider(parseInt(network), scaffoldConfig.alchemyApiKey); + const requestFunc = ({ method, params }: { method: string; params: any }) => alchemyProvider.send(method, params); + const publicClient = usePublicClient({ chainId: parseInt(network), }); - const { setContractAbi, setAbiContractAddress } = useAbiNinjaState(state => ({ + const { setContractAbi, setAbiContractAddress, setImplementationAddress } = useAbiNinjaState(state => ({ setContractAbi: state.setContractAbi, setAbiContractAddress: state.setAbiContractAddress, + setImplementationAddress: state.setImplementationAddress, })); const [isAbiAvailable, setIsAbiAvailable] = useState(false); @@ -49,7 +56,14 @@ const Home: NextPage = () => { const fetchContractAbi = async () => { setIsFetchingAbi(true); try { - const abi = await fetchContractABIFromAnyABI(verifiedContractAddress, parseInt(network)); + const implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); + 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); @@ -80,6 +94,7 @@ const Home: NextPage = () => { } else { setIsAbiAvailable(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [verifiedContractAddress, network, setContractAbi]); useEffect(() => { @@ -114,8 +129,9 @@ const Home: NextPage = () => { useEffect(() => { if (router.pathname === "/") { setContractAbi([]); + setImplementationAddress(""); } - }, [router.pathname, setContractAbi]); + }, [router.pathname, setContractAbi, setImplementationAddress]); const handleLoadContract = () => { if (activeTab === TabName.verifiedContract) { diff --git a/packages/nextjs/services/store/store.ts b/packages/nextjs/services/store/store.ts index 55ce9b39..c0482fb2 100644 --- a/packages/nextjs/services/store/store.ts +++ b/packages/nextjs/services/store/store.ts @@ -17,6 +17,8 @@ type AbiNinjaState = { setContractAbi: (newAbi: Abi) => void; abiContractAddress: Address; setAbiContractAddress: (newAbiContractAddress: Address) => void; + implementationAddress: Address; + setImplementationAddress: (newImplementationAddress: Address) => void; }; export const useGlobalState = create(set => ({ @@ -33,4 +35,6 @@ export const useAbiNinjaState = create(set => ({ setContractAbi: (newAbi: Abi): void => set({ contractAbi: newAbi }), abiContractAddress: "", setAbiContractAddress: (newAddress: Address): void => set({ abiContractAddress: newAddress }), + implementationAddress: "", + setImplementationAddress: (newAddress: Address): void => set({ implementationAddress: newAddress }), })); diff --git a/yarn.lock b/yarn.lock index 7407a364..7c5dbd4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1370,6 +1370,7 @@ __metadata: eslint-config-next: ^13.1.6 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 + evm-proxy-detection: ^1.2.0 next: 13.3.4 next-plausible: ^3.12.0 nextjs-progressbar: ^0.0.16 @@ -4876,6 +4877,13 @@ __metadata: languageName: node linkType: hard +"evm-proxy-detection@npm:^1.2.0": + version: 1.2.0 + resolution: "evm-proxy-detection@npm:1.2.0" + checksum: d9996cbcd22eadd0b1209116d1f5c90ebf063edfe08c71c2f92372040dc004248ab280887a2f6d968d3e4963f40909f7f4cece584992e8e1f6ea136d6e18f2ee + languageName: node + linkType: hard + "execa@npm:^6.1.0": version: 6.1.0 resolution: "execa@npm:6.1.0" From ec1cd1b6b7c8a38ad0c643c1f21ce89d29c9c6ad Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:52:45 +0300 Subject: [PATCH 3/5] Pass undefined to AlchemyProvider (#80) --- packages/nextjs/pages/[contractAddress]/[network].tsx | 2 +- packages/nextjs/pages/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 1aa382ed..c0867360 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -77,7 +77,7 @@ const ContractDetailPage = () => { } try { - const alchemyProvider = new AlchemyProvider(parseInt(network), scaffoldConfig.alchemyApiKey); + const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); const requestFunc = ({ method, params }: { method: string; params: any }) => alchemyProvider.send(method, params); const implementationAddress = await detectProxyTarget(contractAddress, requestFunc); diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 1d5dc492..5141d71f 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -35,7 +35,7 @@ const Home: NextPage = () => { const [isCheckingContractAddress, setIsCheckingContractAddress] = useState(false); const [isContract, setIsContract] = useState(false); - const alchemyProvider = new AlchemyProvider(parseInt(network), scaffoldConfig.alchemyApiKey); + const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); const requestFunc = ({ method, params }: { method: string; params: any }) => alchemyProvider.send(method, params); const publicClient = usePublicClient({ From 52c0381c038d20b05a4761de787de5e66a51b9d8 Mon Sep 17 00:00:00 2001 From: "Shiv Bhonde | shivbhonde.eth" Date: Mon, 8 Apr 2024 12:14:48 +0530 Subject: [PATCH 4/5] allow more networks for proxy contracts (#81) --- .../pages/[contractAddress]/[network].tsx | 27 ++++++++++++++----- packages/nextjs/pages/index.tsx | 27 ++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index c0867360..22a19267 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { AlchemyProvider } from "@ethersproject/providers"; +import { JsonRpcProvider } from "@ethersproject/providers"; import detectProxyTarget from "evm-proxy-detection"; import { ParsedUrlQuery } from "querystring"; -import { Abi, isAddress } from "viem"; +import { Abi, extractChain, isAddress } from "viem"; import * as chains from "viem/chains"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; @@ -24,6 +24,8 @@ type ContractData = { address: string; }; +type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; + const ContractDetailPage = () => { const router = useRouter(); const { contractAddress, network } = router.query as ParsedQueryContractDetailsPage; @@ -77,10 +79,23 @@ const ContractDetailPage = () => { } try { - const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); - const requestFunc = ({ method, params }: { method: string; params: any }) => - alchemyProvider.send(method, params); - const implementationAddress = await detectProxyTarget(contractAddress, requestFunc); + const chain = extractChain({ + id: parseInt(network) as AllowedNetwork, + chains: Object.values(scaffoldConfig.targetNetworks), + }); + // @ts-expect-error this might be present or might not be + const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; + let implementationAddress = undefined; + if (alchmeyRPCURL) { + const alchemyProvider = new JsonRpcProvider( + `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, + parseInt(network), + ); + const requestFunc = ({ method, params }: { method: string; params: any }) => + alchemyProvider.send(method, params); + implementationAddress = await detectProxyTarget(contractAddress, requestFunc); + } + if (implementationAddress) { setImplementationAddress(implementationAddress); } diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 5141d71f..4d12c041 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -2,10 +2,10 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { AlchemyProvider } from "@ethersproject/providers"; +import { JsonRpcProvider } from "@ethersproject/providers"; import detectProxyTarget from "evm-proxy-detection"; import type { NextPage } from "next"; -import { Address, isAddress } from "viem"; +import { Address, extractChain, isAddress } from "viem"; import { usePublicClient } from "wagmi"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniFooter } from "~~/components/MiniFooter"; @@ -21,6 +21,8 @@ enum TabName { addressAbi, } +type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; + const tabValues = Object.values(TabName) as TabName[]; const networks = getTargetNetworks(); @@ -35,9 +37,6 @@ const Home: NextPage = () => { const [isCheckingContractAddress, setIsCheckingContractAddress] = useState(false); const [isContract, setIsContract] = useState(false); - const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); - const requestFunc = ({ method, params }: { method: string; params: any }) => alchemyProvider.send(method, params); - const publicClient = usePublicClient({ chainId: parseInt(network), }); @@ -56,7 +55,23 @@ const Home: NextPage = () => { const fetchContractAbi = async () => { setIsFetchingAbi(true); try { - const implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); + const chain = extractChain({ + id: parseInt(network) as AllowedNetwork, + chains: Object.values(scaffoldConfig.targetNetworks), + }); + // @ts-expect-error this might be present or might not be + const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; + let implementationAddress = undefined; + if (alchmeyRPCURL) { + const alchemyProvider = new JsonRpcProvider( + `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, + parseInt(network), + ); + const requestFunc = ({ method, params }: { method: string; params: any }) => + alchemyProvider.send(method, params); + implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); + } + if (implementationAddress) { setImplementationAddress(implementationAddress); } From 5cd62191a229576afb67822096e5a07ee17fac8a Mon Sep 17 00:00:00 2001 From: "Shiv Bhonde | shivbhonde.eth" Date: Wed, 10 Apr 2024 19:40:53 +0530 Subject: [PATCH 5/5] add useIsMobile hook and udpate copy (#84) Co-authored-by: tokodev --- .../components/scaffold-eth/Contract/ContractReadMethods.tsx | 5 ++++- .../scaffold-eth/Contract/ContractWriteMethods.tsx | 5 ++++- .../scaffold-eth/Contract/WriteOnlyFunctionForm.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx b/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx index 95c43aff..9cd96f0e 100644 --- a/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx +++ b/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx @@ -34,7 +34,10 @@ export const ContractReadMethods = ({ if (!functionsToDisplay.length) { return (
- Please select read methods from the sidebar. + + Please select read methods from the hamburger menu + sidebar. +
); } diff --git a/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx b/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx index 4fa92bbc..d4d2bd18 100644 --- a/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx +++ b/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx @@ -35,7 +35,10 @@ export const ContractWriteMethods = ({ if (!functionsToDisplay.length) { return (
- Please select write methods from the sidebar. + + Please select read methods from the hamburger menu + sidebar. +
); } diff --git a/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx index 6311c642..b01eaf3a 100644 --- a/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx @@ -130,7 +130,7 @@ export const WriteOnlyFunctionForm = ({ wrongNetwork && "tooltip before:content-[attr(data-tip)] before:right-[-10px] before:left-auto before:transform-none" }`} - data-tip={`${wrongNetwork && "Wrong netowrk"}`} + data-tip={`${wrongNetwork && "Wrong network"}`} >