Skip to content

Commit

Permalink
Add useHeimdall hook and prefetch abis from heimdall backend (#166)
Browse files Browse the repository at this point in the history
Co-authored-by: Shiv Bhonde <[email protected]>
  • Loading branch information
portdeveloper and technophile-04 authored Dec 26, 2024
1 parent 941c8f5 commit cc27388
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 29 deletions.
52 changes: 52 additions & 0 deletions packages/nextjs/hooks/useHeimdall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useQuery } from "@tanstack/react-query";
import { Address, isAddress } from "viem";
import { HEIMDALL_API_URL } from "~~/utils/constants";

type UseHeimdallParams = {
contractAddress?: Address;
rpcUrl?: string;
disabled?: boolean;
};

export const useHeimdall = ({ contractAddress, rpcUrl, disabled = false }: UseHeimdallParams) => {
const fetchFromHeimdall = async () => {
if (!contractAddress || !isAddress(contractAddress)) {
throw new Error("Invalid contract address");
}
if (!rpcUrl) {
throw new Error("RPC URL is required");
}

const rpcUrlWithoutHttps = rpcUrl.substring(8);
const response = await fetch(`${HEIMDALL_API_URL}/${contractAddress}?rpc_url=${rpcUrlWithoutHttps}`);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const abi = await response.json();

if (!Array.isArray(abi) || abi.length === 0) {
throw new Error("Failed to fetch ABI from Heimdall");
}

return abi;
};

const {
data: abi,
error,
isLoading,
} = useQuery({
queryKey: ["heimdallAbi", { contractAddress, rpcUrl }],
queryFn: fetchFromHeimdall,
enabled: !disabled && Boolean(contractAddress) && Boolean(rpcUrl) && isAddress(contractAddress as Address),
retry: false,
});

return {
abi,
error,
isLoading,
};
};
49 changes: 25 additions & 24 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { NetworksDropdown } from "~~/components/NetworksDropdown/NetworksDropdow
import { SwitchTheme } from "~~/components/SwitchTheme";
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 { HEIMDALL_API_URL } from "~~/utils/constants";
import { notification } from "~~/utils/scaffold-eth";

enum TabName {
Expand All @@ -32,6 +32,8 @@ const Home: NextPage = () => {
const [localAbiContractAddress, setLocalAbiContractAddress] = useState("");
const [localContractAbi, setLocalContractAbi] = useState("");

const router = useRouter();

const publicClient = usePublicClient({
chainId: parseInt(network),
});
Expand All @@ -42,15 +44,19 @@ const Home: NextPage = () => {
setImplementationAddress: state.setImplementationAddress,
}));

const router = useRouter();

const {
contractData,
error,
isLoading: isFetchingAbi,
implementationAddress,
} = useFetchContractAbi({ contractAddress: verifiedContractAddress, chainId: parseInt(network) });

const { abi: heimdallAbi, isLoading: isHeimdallFetching } = useHeimdall({
contractAddress: localAbiContractAddress as Address,
rpcUrl: publicClient?.chain.rpcUrls.default.http[0],
disabled: network === "31337" || !localAbiContractAddress,
});

const isAbiAvailable = contractData?.abi && contractData.abi.length > 0;

const handleFetchError = useCallback(async () => {
Expand Down Expand Up @@ -129,24 +135,6 @@ const Home: NextPage = () => {
}
};

const fetchAbiFromHeimdall = async (contractAddress: Address) => {
try {
const rpcUrlWithoutHttps = publicClient?.chain.rpcUrls.default.http[0].substring(8);
const response = await fetch(`${HEIMDALL_API_URL}/${contractAddress}?rpc_url=${rpcUrlWithoutHttps}`);
const abi = await response.json();
if (abi.length === 0) {
notification.error("Failed to fetch ABI from Heimdall. Please try again or enter ABI manually.");
return;
}
setContractAbi(abi);
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.");
}
};

return (
<>
<MetaHeader />
Expand Down Expand Up @@ -242,10 +230,23 @@ const Home: NextPage = () => {
</h4>
<button
className="btn btn-primary min-h-fit h-10 px-4 text-base font-semibold border-2 hover:bg-neutral hover:text-primary"
onClick={() => fetchAbiFromHeimdall(localAbiContractAddress as Address)}
disabled={network === "31337"}
onClick={async () => {
if (heimdallAbi) {
setContractAbi(heimdallAbi);
setAbiContractAddress(localAbiContractAddress as Address);
router.push(`/${localAbiContractAddress}/${network}`);
}
}}
disabled={network === "31337" || isHeimdallFetching}
>
{isFetchingAbi ? <span className="loading loading-spinner"></span> : "Decompile (beta)"}
{isHeimdallFetching ? (
<div className="flex items-center gap-2">
<span className="loading loading-spinner loading-xs"></span>
<span>Decompiling contract...</span>
</div>
) : (
"Decompile (beta)"
)}
</button>
</div>
<div className="w-full flex flex-col items-center gap-2">
Expand Down
13 changes: 8 additions & 5 deletions packages/nextjs/services/web3/baseWagmiConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { wagmiConnectors } from "./wagmiConnectors";
import { Chain, createClient, http } from "viem";
import { Chain, createClient, fallback, http } from "viem";
import { hardhat, mainnet } from "viem/chains";
import { createConfig } from "wagmi";
import scaffoldConfig from "~~/scaffold.config";
Expand All @@ -12,23 +12,26 @@ export const enabledChains = targetNetworks.find((network: Chain) => network.id
? targetNetworks
: ([...targetNetworks, mainnet] as const);

export const createWagmiClient = ({ chain }: { chain: Chain }) =>
createClient({
export const createWagmiClient = ({ chain }: { chain: Chain }) => {
const alchemyHttpUrl = getAlchemyHttpUrl(chain.id);
const rpcFallbacks = alchemyHttpUrl ? [http(alchemyHttpUrl), http()] : [http()];

return createClient({
chain: {
...chain,

id: chain.id,
name: chain.name,
nativeCurrency: chain.nativeCurrency,
rpcUrls: chain.rpcUrls,
},
transport: http(getAlchemyHttpUrl(chain.id)),
transport: fallback(rpcFallbacks),
...(chain.id !== (hardhat as Chain).id
? {
pollingInterval: scaffoldConfig.pollingInterval,
}
: {}),
});
};

export const baseWagmiConfig = createConfig({
chains: enabledChains as [Chain, ...Chain[]],
Expand Down

0 comments on commit cc27388

Please sign in to comment.