Skip to content

Commit

Permalink
feat(explorer): show ABI errors in interact page (#3303)
Browse files Browse the repository at this point in the history
  • Loading branch information
karooolis authored Oct 17, 2024
1 parent 9a43e87 commit d4c10c1
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-eyes-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/explorer": patch
---

Interact tab now displays decoded ABI errors for failed transactions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { Coins, Eye, Send } from "lucide-react";
import { AbiFunction } from "viem";
import { Abi, AbiFunction } from "viem";
import { useAccount } from "wagmi";
import { z } from "zod";
import { useState } from "react";
Expand All @@ -20,20 +20,23 @@ export enum FunctionType {
}

type Props = {
abi: AbiFunction;
worldAbi: Abi;
functionAbi: AbiFunction;
};

const formSchema = z.object({
inputs: z.array(z.string()),
value: z.string().optional(),
});

export function FunctionField({ abi }: Props) {
export function FunctionField({ worldAbi, functionAbi }: Props) {
const operationType: FunctionType =
abi.stateMutability === "view" || abi.stateMutability === "pure" ? FunctionType.READ : FunctionType.WRITE;
functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure"
? FunctionType.READ
: FunctionType.WRITE;
const [result, setResult] = useState<string | null>(null);
const { openConnectModal } = useConnectModal();
const mutation = useContractMutation({ abi, operationType });
const mutation = useContractMutation({ worldAbi, functionAbi, operationType });
const account = useAccount();

const form = useForm<z.infer<typeof formSchema>>({
Expand All @@ -58,23 +61,23 @@ export function FunctionField({ abi }: Props) {
}
}

const inputsLabel = abi?.inputs.map((input) => input.type).join(", ");
const inputsLabel = functionAbi?.inputs.map((input) => input.type).join(", ");
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} id={abi.name} className="space-y-4 pb-4">
<form onSubmit={form.handleSubmit(onSubmit)} id={functionAbi.name} className="space-y-4 pb-4">
<h3 className="pt-4 font-semibold">
<span className="text-orange-500">{abi?.name}</span>
<span className="text-orange-500">{functionAbi?.name}</span>
<span className="opacity-50">{inputsLabel && ` (${inputsLabel})`}</span>
<span className="ml-2 opacity-50">
{abi.stateMutability === "payable" && <Coins className="mr-2 inline-block h-4 w-4" />}
{(abi.stateMutability === "view" || abi.stateMutability === "pure") && (
{functionAbi.stateMutability === "payable" && <Coins className="mr-2 inline-block h-4 w-4" />}
{(functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure") && (
<Eye className="mr-2 inline-block h-4 w-4" />
)}
{abi.stateMutability === "nonpayable" && <Send className="mr-2 inline-block h-4 w-4" />}
{functionAbi.stateMutability === "nonpayable" && <Send className="mr-2 inline-block h-4 w-4" />}
</span>
</h3>

{abi?.inputs.map((input, index) => (
{functionAbi?.inputs.map((input, index) => (
<FormField
key={index}
control={form.control}
Expand All @@ -91,7 +94,7 @@ export function FunctionField({ abi }: Props) {
/>
))}

{abi.stateMutability === "payable" && (
{functionAbi.stateMutability === "payable" && (
<FormField
control={form.control}
name="value"
Expand All @@ -108,8 +111,8 @@ export function FunctionField({ abi }: Props) {
)}

<Button type="submit" disabled={mutation.isPending}>
{(abi.stateMutability === "view" || abi.stateMutability === "pure") && "Read"}
{(abi.stateMutability === "payable" || abi.stateMutability === "nonpayable") && "Write"}
{(functionAbi.stateMutability === "view" || functionAbi.stateMutability === "pure") && "Read"}
{(functionAbi.stateMutability === "payable" || functionAbi.stateMutability === "nonpayable") && "Write"}
</Button>

{result && <pre className="text-md rounded border p-3 text-sm">{result}</pre>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Coins, Eye, Send } from "lucide-react";
import { useQueryState } from "nuqs";
import { AbiFunction } from "viem";
import { useDeferredValue } from "react";
import { useDeferredValue, useMemo } from "react";
import { Input } from "../../../../../../components/ui/Input";
import { Separator } from "../../../../../../components/ui/Separator";
import { Skeleton } from "../../../../../../components/ui/Skeleton";
Expand All @@ -12,14 +12,17 @@ import { useHashState } from "../../../../hooks/useHashState";
import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery";
import { FunctionField } from "./FunctionField";

export function Form() {
export function InteractForm() {
const [hash] = useHashState();
const { data, isFetched } = useWorldAbiQuery();
const [filterValue, setFilterValue] = useQueryState("function", { defaultValue: "" });
const deferredFilterValue = useDeferredValue(filterValue);
const filteredFunctions = data?.abi?.filter(
(item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
);
const filteredFunctions = useMemo(() => {
if (!data?.abi) return [];
return data.abi.filter(
(item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
);
}, [data?.abi, deferredFilterValue]);

return (
<>
Expand Down Expand Up @@ -87,9 +90,10 @@ export function Form() {
</>
)}

{filteredFunctions?.map((abi) => {
return <FunctionField key={JSON.stringify(abi)} abi={abi as AbiFunction} />;
})}
{data?.abi &&
filteredFunctions.map((abi) => (
<FunctionField key={JSON.stringify(abi)} worldAbi={data.abi} functionAbi={abi} />
))}
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Metadata } from "next";
import { Form } from "./Form";
import { InteractForm } from "./InteractForm";

export const metadata: Metadata = {
title: "Interact",
};

export default async function InteractPage() {
return <Form />;
return <InteractForm />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { useChain } from "../../../../hooks/useChain";
import { FunctionType } from "./FunctionField";

type UseContractMutationProps = {
abi: AbiFunction;
worldAbi: Abi;
functionAbi: AbiFunction;
operationType: FunctionType;
};

export function useContractMutation({ abi, operationType }: UseContractMutationProps) {
export function useContractMutation({ worldAbi, functionAbi, operationType }: UseContractMutationProps) {
const { worldAddress } = useParams();
const { id: chainId } = useChain();
const queryClient = useQueryClient();
Expand All @@ -23,19 +24,19 @@ export function useContractMutation({ abi, operationType }: UseContractMutationP
mutationFn: async ({ inputs, value }: { inputs: unknown[]; value?: string }) => {
if (operationType === FunctionType.READ) {
const result = await readContract(wagmiConfig, {
abi: [abi] as Abi,
abi: worldAbi,
address: worldAddress as Hex,
functionName: abi.name,
functionName: functionAbi.name,
args: inputs,
chainId,
});

return { result };
} else {
const txHash = await writeContract(wagmiConfig, {
abi: [abi] as Abi,
abi: worldAbi,
address: worldAddress as Hex,
functionName: abi.name,
functionName: functionAbi.name,
args: inputs,
...(value && { value: BigInt(value) }),
chainId,
Expand Down

0 comments on commit d4c10c1

Please sign in to comment.