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] 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}
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"