Skip to content

Commit

Permalink
Decompile ABI using a heimdall-rs backend
Browse files Browse the repository at this point in the history
  • Loading branch information
portdeveloper committed Mar 10, 2024
1 parent 4d72834 commit d99b906
Showing 1 changed file with 123 additions and 77 deletions.
200 changes: 123 additions & 77 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ enum TabName {
addressAbi,
}

enum AbiInputMethod {
Manual,
Heimdall,
}

const tabValues = Object.values(TabName) as TabName[];

const networks = getTargetNetworks();
Expand All @@ -29,8 +34,7 @@ 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 [abiInputMethod, setAbiInputMethod] = useState(AbiInputMethod.Manual);

const publicClient = usePublicClient({
chainId: parseInt(network),
Expand Down Expand Up @@ -64,7 +68,25 @@ 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) {
notification.error(
"The contract is not verified on Etherscan. Please provide ABI manually or decompile ABI(experimental)",
{
duration: 10000,
position: "bottom-left",
},
);
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 @@ -82,45 +104,16 @@ const Home: NextPage = () => {
}
}, [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]);

useEffect(() => {
if (router.pathname === "/") {
setContractAbi([]);
}
}, [router.pathname, setContractAbi]);

const handleLoadContract = () => {
if (activeTab === TabName.verifiedContract) {
if (isAbiAvailable) {
router.push(`/${verifiedContractAddress}/${network}`);
} else if (activeTab === TabName.addressAbi) {
} else if (localContractAbi.length > 0) {
try {
setContractAbi(parseAndCorrectJSON(localContractAbi));
} catch (error) {
Expand All @@ -129,6 +122,26 @@ const Home: NextPage = () => {
}
setAbiContractAddress(localAbiContractAddress);
router.push(`/${localAbiContractAddress}/${network}`);
} else {
fetchAbiFromHeimdall(localAbiContractAddress);
}
};

const fetchAbiFromHeimdall = async (contractAddress: string) => {
setIsFetchingAbi(true);
try {
const response = await fetch(`https://heimdall-api-cool-frog-2068.fly.dev/${network}/${contractAddress}`);
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);
}
};

Expand All @@ -145,31 +158,6 @@ const Home: NextPage = () => {
<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"
}`}
onClick={() => setActiveTab(TabName.addressAbi)}
>
Address + ABI
</a>
</div>

<div className="relative min-h-[150px] w-full overflow-hidden">
<div className="flex">
{tabValues.map(tabValue => (
Expand All @@ -187,7 +175,7 @@ const Home: NextPage = () => {
<div className="my-4">
<AddressInput
value={verifiedContractAddress}
placeholder="Verified contract address"
placeholder="Contract address"
onChange={setVerifiedContractAddress}
/>
<div className="flex flex-col text-sm">
Expand Down Expand Up @@ -218,38 +206,96 @@ const Home: NextPage = () => {
</div>
</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 className="flex w-full flex-col gap-3">
{/* Tab navigation */}

<div className="flex w-full border-b">
<div
role="tab"
className={`inline-block px-4 py-2 text-xs font-medium text-center w-1/2 cursor-pointer ${
abiInputMethod === AbiInputMethod.Manual
? "border-b-2 border-purple-500 text-purple-500"
: "border-b-2 border-transparent text-gray-500 hover:text-purple-700"
}`}
onClick={() => setAbiInputMethod(AbiInputMethod.Manual)}
>
Input ABI Manually
</div>
<div
role="tab"
className={`inline-block px-4 py-2 text-xs font-medium text-center w-1/2 cursor-pointer ${
abiInputMethod === AbiInputMethod.Heimdall
? "border-b-2 border-purple-500 text-purple-500"
: "border-b-2 border-transparent text-gray-500 hover:text-purple-700"
}`}
onClick={() => setAbiInputMethod(AbiInputMethod.Heimdall)}
>
Decompile ABI
</div>
</div>

{/* Content based on active tab */}
{abiInputMethod === AbiInputMethod.Manual ? (
<>
<AddressInput
placeholder="Contract address"
value={localAbiContractAddress}
onChange={setLocalAbiContractAddress}
/>
<InputBase
placeholder="Contract ABI (json format)"
value={localContractAbi}
onChange={setLocalContractAbi}
/>
</>
) : (
<>
<AddressInput
placeholder="Contract address"
value={localAbiContractAddress}
onChange={setLocalAbiContractAddress}
/>
</>
)}
</div>
)}
</div>
))}
</div>
{activeTab !== TabName.verifiedContract && abiInputMethod === AbiInputMethod.Heimdall && (
<div className="text-xs mt-24 text-center">
Warning: this feature is experimental. You may lose funds if you interact with contracts using this
feature.
</div>
)}
</div>

{activeTab === TabName.addressAbi && (
<div
className="text-xs link link-primary absolute top-96"
onClick={() => {
setActiveTab(TabName.verifiedContract);
setVerifiedContractAddress("");
}}
>
← go back
</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))
abiInputMethod === AbiInputMethod.Manual &&
(!localContractAbi || localContractAbi.length === 0)) ||
(activeTab === TabName.addressAbi &&
abiInputMethod === AbiInputMethod.Heimdall &&
!isAddress(localAbiContractAddress))
}
>
{isFetchingAbi || isCheckingContractAddress ? (
<span className="loading loading-spinner"></span>
) : (
"Load Contract"
)}
{isFetchingAbi ? <span className="loading loading-spinner"></span> : "Load Contract"}
</button>
</div>
<MiniFooter />
Expand Down

0 comments on commit d99b906

Please sign in to comment.