Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decompile ABI using a heimdall-rs backend #71

Merged
merged 27 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d99b906
Decompile ABI using a heimdall-rs backend
portdeveloper Mar 10, 2024
ce5d6e8
Better placement of go back button and input
portdeveloper Mar 12, 2024
30f3d9e
Merge branch 'main' into ui/heimdallrs-steps
portdeveloper Mar 13, 2024
a617119
Update backend link
portdeveloper Mar 13, 2024
cf63ece
Fix eslint warning
portdeveloper Mar 15, 2024
63b23db
Add additionalClasses param to networksdropdown
portdeveloper Mar 28, 2024
a95a6a4
Update ui, merging the current style with the suggested style
portdeveloper Mar 28, 2024
f13f8ec
Merge branch 'main' into ui/heimdallrs-steps
Pabl0cks Mar 28, 2024
a2db599
Missing padding change from PR #75
Pabl0cks Mar 28, 2024
5a75eca
Remove err notif saying contract not verified
portdeveloper Mar 29, 2024
7f98e23
Disable network dropdown in second step(abi/heimdall)
portdeveloper Mar 29, 2024
267c0af
Small spacing tweaks on new screen
Pabl0cks Mar 29, 2024
f07871f
Tweak homepage spacing
Pabl0cks Mar 29, 2024
5c9c595
Fix dropdown showing scrollbar on homepage
Pabl0cks Mar 29, 2024
3345c3c
Fix footer not being clickable
Pabl0cks Mar 29, 2024
c884c0c
Show footer in new screen
Pabl0cks Mar 29, 2024
33961b8
Set same hover effect to new screen buttons
Pabl0cks Mar 29, 2024
0cba3a0
remove goerli + OPGoerli
technophile-04 Apr 1, 2024
b475868
remove additionalClasses from NetworksDropdown
technophile-04 Apr 1, 2024
fc63d46
Check abi length and return if its 0
portdeveloper Apr 3, 2024
280a9fc
Merge branch 'main' into ui/heimdallrs-steps
portdeveloper Apr 13, 2024
7455cef
Add missing import + dep to dep array
portdeveloper Apr 13, 2024
73276ed
Merge branch 'main' into ui/heimdallrs-steps
portdeveloper Apr 15, 2024
8b222cc
Remove scaffoldConfig import
portdeveloper Apr 15, 2024
59ede15
Add heimdall backend url as env var
portdeveloper Apr 17, 2024
fd66bc0
remove https:// from heimdall url
technophile-04 Apr 19, 2024
1592c0f
fix bug from (#86) default network a mainnet at 0th index
technophile-04 Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/nextjs/components/MiniFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BuidlGuidlLogo } from "~~/components/assets/BuidlGuidlLogo";

export const MiniFooter = () => {
return (
<div className="flex justify-center items-center gap-1 text-xs w-full mt-10">
<div className="flex justify-center items-center gap-1 text-xs w-full mt-10 z-10">
<div className="mb-1">
<a href="https://github.com/BuidlGuidl/abi.ninja" target="_blank" rel="noreferrer" className="link">
Fork me
Expand Down
5 changes: 4 additions & 1 deletion packages/nextjs/components/NetworksDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const NetworksDropdown = ({ onChange }: { onChange: (options: any) => any
onChange={onChange}
components={{ Option: IconOption }}
isSearchable={!isMobile}
className="text-sm w-44 max-w-xs bg-white relative"
className="max-w-xs bg-white relative text-sm w-44"
theme={theme => ({
...theme,
colors: {
Expand All @@ -88,6 +88,9 @@ export const NetworksDropdown = ({ onChange }: { onChange: (options: any) => any
primary: "#551d98",
},
})}
styles={{
menuList: provided => ({ ...provided, maxHeight: 280, overflow: "auto" }),
}}
/>
);
};
311 changes: 159 additions & 152 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { useRouter } from "next/router";
import type { NextPage } from "next";
import { Address, isAddress } from "viem";
import { usePublicClient } from "wagmi";
import { ChevronLeftIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { MetaHeader } from "~~/components/MetaHeader";
import { MiniFooter } from "~~/components/MiniFooter";
import { NetworksDropdown } from "~~/components/NetworksDropdown";
import { AddressInput, InputBase } from "~~/components/scaffold-eth";
import { AddressInput } from "~~/components/scaffold-eth";
import { useAbiNinjaState } from "~~/services/store/store";
import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan, parseAndCorrectJSON } from "~~/utils/abi";
import { getTargetNetworks, notification } from "~~/utils/scaffold-eth";
Expand All @@ -29,8 +30,6 @@ const Home: NextPage = () => {
const [localAbiContractAddress, setLocalAbiContractAddress] = useState("");
const [localContractAbi, setLocalContractAbi] = useState("");
const [isFetchingAbi, setIsFetchingAbi] = useState(false);
const [isCheckingContractAddress, setIsCheckingContractAddress] = useState(false);
const [isContract, setIsContract] = useState(false);

const publicClient = usePublicClient({
chainId: parseInt(network),
Expand Down Expand Up @@ -64,7 +63,18 @@ const Home: NextPage = () => {
} catch (etherscanError: any) {
setIsAbiAvailable(false);
console.error("Error fetching ABI from Etherscan: ", etherscanError);
notification.error(etherscanError.message || "Error occurred while fetching ABI");

const bytecode = await publicClient.getBytecode({
address: verifiedContractAddress,
});
const isContract = Boolean(bytecode) && bytecode !== "0x";

if (isContract) {
setLocalAbiContractAddress(verifiedContractAddress);
setActiveTab(TabName.addressAbi);
} else {
notification.error("Address is not a contract, are you sure you are on the correct chain?");
}
}
} finally {
setIsFetchingAbi(false);
Expand All @@ -73,43 +83,14 @@ const Home: NextPage = () => {

if (isAddress(verifiedContractAddress)) {
if (network === "31337") {
notification.error("To interact with Localhost contracts, please use Address + ABI tab");
setActiveTab(TabName.addressAbi);
return;
}
fetchContractAbi();
} else {
setIsAbiAvailable(false);
}
}, [verifiedContractAddress, network, setContractAbi]);

useEffect(() => {
const checkContract = async () => {
if (!isAddress(localAbiContractAddress)) {
setIsContract(false);
return;
}

setIsCheckingContractAddress(true);
try {
const bytecode = await publicClient.getBytecode({
address: localAbiContractAddress,
});
const isContract = Boolean(bytecode) && bytecode !== "0x";
setIsContract(isContract);

if (!isContract) {
notification.error("Address is not a contract");
}
} catch (e) {
notification.error("Error while checking for contract address");
setIsContract(false);
} finally {
setIsCheckingContractAddress(false);
}
};

checkContract();
}, [localAbiContractAddress, publicClient]);
}, [verifiedContractAddress, network, setContractAbi, publicClient]);

useEffect(() => {
if (router.pathname === "/") {
Expand All @@ -118,139 +99,165 @@ const Home: NextPage = () => {
}, [router.pathname, setContractAbi]);

const handleLoadContract = () => {
if (activeTab === TabName.verifiedContract) {
if (isAbiAvailable) {
router.push(`/${verifiedContractAddress}/${network}`);
} else if (activeTab === TabName.addressAbi) {
try {
setContractAbi(parseAndCorrectJSON(localContractAbi));
} catch (error) {
notification.error("Invalid ABI format. Please ensure it is a valid JSON.");
return;
}
setAbiContractAddress(localAbiContractAddress);
}
};

const handleUserProvidedAbi = () => {
if (!localContractAbi) {
notification.error("Please provide an ABI.");
return;
}
try {
const parsedAbi = parseAndCorrectJSON(localContractAbi);
setContractAbi(parsedAbi);
router.push(`/${localAbiContractAddress}/${network}`);
notification.success("ABI successfully loaded.");
} catch (error) {
notification.error("Invalid ABI format. Please ensure it is a valid JSON.");
}
};

const fetchAbiFromHeimdall = async (contractAddress: string) => {
setIsFetchingAbi(true);
try {
const response = await fetch(`https://heimdall-api.fly.dev/${network}/${contractAddress}`);
portdeveloper marked this conversation as resolved.
Show resolved Hide resolved
const abi = await response.json();
setContractAbi(abi);
setIsAbiAvailable(true);
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.");
setIsAbiAvailable(false);
} finally {
setIsFetchingAbi(false);
}
};

return (
<>
<MetaHeader />
<div className="flex flex-grow items-center justify-center bg-base-100">
<div className="flex h-screen w-full flex-col items-center justify-center rounded-2xl bg-white pb-4 lg:h-[650px] lg:w-[450px] lg:justify-between lg:shadow-xl">
<div className="mt-10 flex flex-col items-center justify-center lg:w-10/12">
<Image src="/logo_inv.svg" alt="logo" width={128} height={128} className="mb-4" />
<h2 className="mb-0 text-5xl font-bold">ABI Ninja</h2>
<p className="">Interact with any contract on Ethereum</p>
<div className="my-4">
<NetworksDropdown onChange={option => setNetwork(option ? option.value.toString() : "")} />
</div>

<div role="tablist" className="flex w-full border-b">
<a
role="tab"
className={`inline-block px-2 py-2 text-sm w-full font-medium text-center border-b-2 hover:cursor-pointer ${
activeTab === TabName.verifiedContract
? "border-purple-500"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
}`}
onClick={() => setActiveTab(TabName.verifiedContract)}
>
Verified Contract
</a>
<a
role="tab"
className={`inline-block px-4 py-2 text-sm w-full font-medium text-center border-b-2 hover:cursor-pointer ${
activeTab === TabName.addressAbi
? "border-purple-500"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
<div className="flex h-screen relative overflow-x-hidden w-full flex-col items-center justify-center rounded-2xl bg-white pb-4 lg:h-[650px] lg:w-[450px] lg:justify-between lg:shadow-xl">
<div className="flex-grow flex flex-col items-center justify-center lg:w-full">
{tabValues.map(tabValue => (
<div
key={tabValue}
className={`absolute flex flex-col justify-center inset-0 w-full transition-transform duration-300 ease-in-out px-1 ${
activeTab === tabValue
? "translate-x-0"
: activeTab < tabValue
? "translate-x-full"
: "-translate-x-full"
}`}
onClick={() => setActiveTab(TabName.addressAbi)}
>
Address + ABI
</a>
</div>
{tabValue === TabName.verifiedContract ? (
<div className="my-16 flex flex-col items-center justify-center">
<Image src="/logo_inv.svg" alt="logo" width={128} height={128} className="mb-4" />
<h2 className="mb-0 text-5xl font-bold">ABI Ninja</h2>
<p>Interact with any contract on Ethereum</p>
<div className="mt-4">
<NetworksDropdown onChange={option => setNetwork(option ? option.value.toString() : "")} />
</div>

<div className="relative min-h-[150px] w-full overflow-hidden">
<div className="flex">
{tabValues.map(tabValue => (
<div
key={tabValue}
className={`absolute inset-0 w-full transition-transform duration-300 ease-in-out px-1 ${
activeTab === tabValue
? "translate-x-0"
: activeTab < tabValue
? "translate-x-full"
: "-translate-x-full"
}`}
>
{tabValue === TabName.verifiedContract ? (
<div className="my-4">
<AddressInput
value={verifiedContractAddress}
placeholder="Verified contract address"
onChange={setVerifiedContractAddress}
/>
<div className="flex flex-col text-sm">
<div className="mb-2 mt-4 text-center font-semibold">Quick Access</div>
<div className="flex justify-around">
<Link
href="/0x6B175474E89094C44Da98b954EedeAC495271d0F/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
DAI
</Link>
<Link
href="/0xde30da39c46104798bb5aa3fe8b9e0e1f348163f/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
Gitcoin
</Link>
<Link
href="/0x00000000006c3852cbef3e08e8df289169ede581/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
Opensea
</Link>
</div>
</div>
<div className="w-10/12 my-8">
<AddressInput
placeholder="Contract address"
value={verifiedContractAddress}
onChange={setVerifiedContractAddress}
/>
</div>

<button
className="btn btn-primary px-8 text-base border-2 hover:bg-white hover:text-primary"
onClick={handleLoadContract}
disabled={!isAbiAvailable}
>
{isFetchingAbi ? <span className="loading loading-spinner"></span> : "Load Contract"}
</button>
<div className="flex flex-col text-sm w-4/5 mb-10 mt-14">
<div className="mb-2 text-center font-semibold">Quick Access</div>
<div className="flex justify-center w-full">
<Link
href="/0x6B175474E89094C44Da98b954EedeAC495271d0F/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
DAI
</Link>
<Link
href="/0xde30da39c46104798bb5aa3fe8b9e0e1f348163f/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
Gitcoin
</Link>
<Link
href="/0x00000000006c3852cbef3e08e8df289169ede581/1"
passHref
className="link w-1/3 text-center text-purple-700 no-underline"
>
Opensea
</Link>
</div>
) : (
<div className="my-4 flex w-full flex-col gap-3">
<AddressInput
placeholder="Contract address"
value={localAbiContractAddress}
onChange={setLocalAbiContractAddress}
/>
<InputBase
placeholder="Contract ABI (json format)"
value={localContractAbi}
onChange={setLocalContractAbi}
/>
</div>
</div>
) : (
<div className="flex w-full flex-col items-center gap-3 p-6">
<div className="flex justify-center mb-6">
<button
className="btn btn-ghost absolute left-4 px-2 btn-primary"
onClick={() => {
setActiveTab(TabName.verifiedContract);
setVerifiedContractAddress("");
}}
>
<ChevronLeftIcon className="h-4 w-4" />
Go back
</button>
<Image src="/logo_inv.svg" alt="logo" width={64} height={64} className="mb-2" />
</div>

<div className="flex flex-col items-center w-4/5 border-b-2 pb-8">
<div className="flex justify-center items-center gap-1">
<MagnifyingGlassIcon className="h-5 w-5" />
<h1 className="font-semibold text-lg mb-0">Contract not verified</h1>
</div>
)}
<p className="bg-slate-100 px-2 rounded-md border border-slate-300 text-sm shadow-sm">
{localAbiContractAddress}
</p>
<h4 className="text-center mb-6 font-semibold leading-tight">
You can decompile the contract (beta) or import the ABI manually below.
</h4>
<button
className="btn btn-primary border-2 hover:bg-white hover:text-primary"
onClick={() => fetchAbiFromHeimdall(localAbiContractAddress)}
>
{isFetchingAbi ? <span className="loading loading-spinner"></span> : "Decompile (beta)"}
</button>
</div>
<div className="w-full flex flex-col items-center gap-2">
<h1 className="mt-2 font-semibold text-lg">Manually import ABI</h1>
<textarea
className="textarea bg-slate-100 w-4/5 h-24 mb-4 resize-none"
placeholder="Paste contract ABI in JSON format here"
value={localContractAbi}
onChange={e => setLocalContractAbi(e.target.value)}
></textarea>
<button
className="btn btn-primary border-2 mb-12 hover:bg-white hover:text-primary"
onClick={handleUserProvidedAbi}
>
Import ABI
</button>
</div>
</div>
))}
)}
</div>
</div>

<button
className="btn btn-primary px-8 text-base border-2 hover:bg-white hover:text-primary"
onClick={handleLoadContract}
disabled={
(activeTab === TabName.verifiedContract && (!isAbiAvailable || !verifiedContractAddress)) ||
(activeTab === TabName.addressAbi &&
(!isContract || !localContractAbi || localContractAbi.length === 0))
}
>
{isFetchingAbi || isCheckingContractAddress ? (
<span className="loading loading-spinner"></span>
) : (
"Load Contract"
)}
</button>
))}
</div>
<MiniFooter />
</div>
Expand Down
Loading
Loading