Skip to content

Commit

Permalink
Adding cache support for ABI fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
Jør∂¡ committed Mar 13, 2024
1 parent d14484c commit 6b2d327
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 55 deletions.
2 changes: 1 addition & 1 deletion components/actions/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const ActionCard = function ({ action, idx }: ActionCardProps) {
<div>
<div className="w-full flex flex-row space-x-10">
<div>
<h3 className="font-semibold">Contract call</h3>
<h3 className="font-semibold">Contract</h3>
<p>
<AddressText>{action.to}</AddressText>
</p>
Expand Down
3 changes: 2 additions & 1 deletion components/input/function-call-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const FunctionCallForm: FC<FunctionCallFormProps> = ({
value,
data,
});
setTargetContract("");
};

return (
Expand All @@ -47,7 +48,7 @@ export const FunctionCallForm: FC<FunctionCallFormProps> = ({
</div>
</Then>
<ElseIf not={targetContract}>
<p>Enter the address of the contract to interact with</p>
<p>Enter the address of the contract to call in a new action</p>
</ElseIf>
<ElseIf not={isAddress(targetContract)}>
<AlertInline
Expand Down
108 changes: 59 additions & 49 deletions hooks/useAbi.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,82 @@
import { useEffect, useState, useCallback } from "react";
import { Address } from "viem";
import { whatsabi } from "@shazow/whatsabi";
import { usePublicClient } from "wagmi";
import { AbiFunction } from "abitype";
import { useQuery } from "@tanstack/react-query";
import { isAddress } from "@/utils/evm";
import { PUB_CHAIN, PUB_ETHERSCAN_API_KEY } from "@/constants";
import { useAlertContext } from "@/context/AlertContext";

export const useAbi = (contractAddress: Address) => {
const { addAlert } = useAlertContext();
const publicClient = usePublicClient({ chainId: PUB_CHAIN.id });
const [abi, setAbi] = useState<AbiFunction[]>();
const [isLoading, setIsLoading] = useState<boolean>(false);

useEffect(() => {
if (!publicClient) return;
else if (!isAddress(contractAddress)) return;
const {
data: abi,
isLoading,
error,
} = useQuery<AbiFunction[], Error>({
queryKey: [contractAddress || "", !!publicClient],
queryFn: () => {
if (!contractAddress || !isAddress(contractAddress) || !publicClient) {
return Promise.resolve([]);
}

setIsLoading(true);
const abiLoader = new whatsabi.loaders.EtherscanABILoader({
apiKey: PUB_ETHERSCAN_API_KEY,
});
const abiLoader = new whatsabi.loaders.EtherscanABILoader({
apiKey: PUB_ETHERSCAN_API_KEY,
});

whatsabi
.autoload(contractAddress!, {
provider: publicClient,
abiLoader,
followProxies: true,
enableExperimentalMetadata: true,
})
.then(({ abi }) => {
const functionItems: AbiFunction[] = [];
for (const item of abi) {
if (item.type === "event") continue;
return whatsabi
.autoload(contractAddress!, {
provider: publicClient,
abiLoader,
followProxies: true,
enableExperimentalMetadata: true,
})
.then(({ abi }) => {
const functionItems: AbiFunction[] = [];
for (const item of abi) {
if (item.type === "event") continue;

functionItems.push({
name: (item as any).name ?? "(function)",
inputs: item.inputs ?? [],
outputs: item.outputs ?? [],
stateMutability: item.stateMutability ?? "payable",
type: item.type,
});
}
functionItems.sort((a, b) => {
if (
["pure", "view"].includes(a.stateMutability) &&
["pure", "view"].includes(b.stateMutability)
) {
functionItems.push({
name: (item as any).name ?? "(function)",
inputs: item.inputs ?? [],
outputs: item.outputs ?? [],
stateMutability: item.stateMutability ?? "payable",
type: item.type,
});
}
functionItems.sort((a, b) => {
if (
["pure", "view"].includes(a.stateMutability) &&
["pure", "view"].includes(b.stateMutability)
) {
return 0;
} else if (["pure", "view"].includes(a.stateMutability)) return 1;
else if (["pure", "view"].includes(b.stateMutability)) return -1;
return 0;
} else if (["pure", "view"].includes(a.stateMutability)) return 1;
else if (["pure", "view"].includes(b.stateMutability)) return -1;
return 0;
});
setAbi(functionItems);
setIsLoading(false);
})
.catch((err) => {
console.error(err);
setIsLoading(false);
addAlert("Cannot fetch", {
description: "The details of the contract could not be fetched",
type: "error",
});
return functionItems;
})
.catch((err) => {
console.error(err);
addAlert("Cannot fetch", {
description: "The details of the contract could not be fetched",
type: "error",
});
throw err;
});
});
}, [contractAddress]);
},
retry: 4,
refetchOnMount: false,
refetchOnReconnect: false,
retryOnMount: true,
staleTime: Infinity,
});

return {
abi: abi ?? [],
isLoading,
error,
};
};
6 changes: 2 additions & 4 deletions hooks/useMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { fetchJsonFromIpfs } from "@/utils/ipfs";
import { JsonValue } from "@/utils/types";
import { useQuery } from "@tanstack/react-query";
import { fromHex } from "viem";

type JsonValue = string | number | boolean;
type JsonObject = JsonValue | Record<string, JsonValue> | Array<JsonValue>;

export function useMetadata<T = JsonObject>(ipfsUri?: string) {
export function useMetadata<T = JsonValue>(ipfsUri?: string) {
const { data, isLoading, isSuccess, error } = useQuery<T, Error>({
queryKey: [ipfsUri || ""],
queryFn: () => {
Expand Down
8 changes: 8 additions & 0 deletions utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ export interface IAlert {
explorerLink?: string;
dismissTimeout?: ReturnType<typeof setTimeout>;
}

// General types

type JsonLiteral = string | number | boolean;
export type JsonValue =
| JsonLiteral
| Record<string, JsonLiteral>
| Array<JsonLiteral>;

0 comments on commit 6b2d327

Please sign in to comment.