Skip to content

Commit 13071c4

Browse files
karooolisfrolic
andauthored
feat(explorer): use systems abis in decode form (#3646)
Co-authored-by: Kevin Ingersoll <[email protected]>
1 parent 490159e commit 13071c4

File tree

16 files changed

+173
-53
lines changed

16 files changed

+173
-53
lines changed

.changeset/khaki-walls-enjoy.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@latticexyz/explorer": patch
3+
---
4+
5+
- Added the `/system-abis` endpoint to retrieve ABIs by system IDs.
6+
- The search form for decoding selectors now uses all system ABIs for complete results.
7+
- The `ABI` page has been renamed to `Decode`.

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/abi/AbiExplorer.tsx renamed to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/decode/AbiExplorer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function AbiExplorer() {
1616

1717
return (
1818
<div className="space-y-4">
19-
<h4 className="font-semibold uppercase">ABI</h4>
19+
<h4 className="font-semibold uppercase">World ABI</h4>
2020
<pre className="text-md relative mb-4 rounded border border-white/20 p-3 text-sm">
2121
<JsonView src={data?.abi} theme="a11y" />
2222
<CopyButton value={JSON.stringify(data?.abi, null, 2)} className="absolute right-1.5 top-1.5" />

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/abi/DecodeForm.tsx renamed to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/decode/DecodeForm.tsx

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { AbiEvent, AbiFunction, toFunctionSelector } from "viem";
3+
import { AbiFunction, AbiItem, toFunctionSelector } from "viem";
44
import { formatAbiItem } from "viem/utils";
55
import * as z from "zod";
66
import { useState } from "react";
@@ -22,36 +22,50 @@ import {
2222
import { Input } from "../../../../../../components/ui/Input";
2323
import { Skeleton } from "../../../../../../components/ui/Skeleton";
2424
import { cn } from "../../../../../../utils";
25+
import { useSystemAbisQuery } from "../../../../queries/useSystemAbisQuery";
2526
import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery";
2627
import { getErrorSelector } from "./getErrorSelector";
2728

29+
type AbiError = AbiItem & { type: "error" };
30+
2831
const formSchema = z.object({
2932
selector: z.string().min(1).optional(),
3033
});
3134

35+
function isAbiFunction(item: AbiItem): item is AbiFunction {
36+
return item.type === "function";
37+
}
38+
39+
function isAbiError(item: AbiItem): item is AbiItem & { type: "error" } {
40+
return item.type === "error";
41+
}
42+
3243
export function DecodeForm() {
33-
const { data, isLoading } = useWorldAbiQuery();
44+
const { data: worldData, isLoading: isWorldAbiLoading } = useWorldAbiQuery();
45+
const { data: systemData, isLoading: isSystemAbisLoading } = useSystemAbisQuery();
46+
const [abiItem, setAbiItem] = useState<AbiFunction | AbiError>();
3447
const form = useForm<z.infer<typeof formSchema>>({
3548
resolver: zodResolver(formSchema),
3649
});
37-
const [result, setResult] = useState<AbiFunction | AbiEvent>();
3850

3951
function onSubmit({ selector }: z.infer<typeof formSchema>) {
40-
const items = data?.abi.filter((item) => item.type === "function" || item.type === "error");
41-
const abiItem = items?.find((item) => {
42-
if (item.type === "function") {
52+
const worldAbi = worldData?.abi || [];
53+
const systemsAbis = systemData ? Object.values(systemData) : [];
54+
const abis = [worldAbi, ...systemsAbis].flat();
55+
56+
const abiItem = abis.find((item): item is AbiFunction | AbiError => {
57+
if (isAbiFunction(item)) {
4358
return toFunctionSelector(item) === selector;
44-
} else if (item.type === "error") {
59+
} else if (isAbiError(item)) {
4560
return getErrorSelector(item) === selector;
4661
}
47-
4862
return false;
4963
});
5064

51-
setResult(abiItem);
65+
setAbiItem(abiItem);
5266
}
5367

54-
if (isLoading) {
68+
if (isWorldAbiLoading || isSystemAbisLoading) {
5569
return <Skeleton className="h-[152px] w-full" />;
5670
}
5771

@@ -76,14 +90,14 @@ export function DecodeForm() {
7690
{form.formState.isSubmitted && (
7791
<pre
7892
className={cn("text-md relative mt-4 rounded border border-white/20 p-3 text-sm", {
79-
"border-red-400 bg-red-100": !result,
93+
"border-red-400 bg-red-100": !abiItem,
8094
})}
8195
>
82-
{result ? (
96+
{abiItem ? (
8397
<>
84-
<span className="mr-2 text-sm opacity-50">{result.type === "function" ? "function" : "error"}</span>
85-
<span>{formatAbiItem(result)}</span>
86-
<CopyButton value={JSON.stringify(result, null, 2)} className="absolute right-1.5 top-1.5" />
98+
<span className="mr-2 text-sm opacity-50">{abiItem.type === "function" ? "function" : "error"}</span>
99+
<span>{formatAbiItem(abiItem)}</span>
100+
<CopyButton value={JSON.stringify(abiItem, null, 2)} className="absolute right-1.5 top-1.5" />
87101
</>
88102
) : (
89103
<span className="text-red-700">No matching function or error found for this selector</span>

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/abi/page.tsx renamed to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/decode/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ export default async function UtilsPage() {
1111
return (
1212
<div className="flex h-[calc(100vh-70px)] flex-col space-y-8">
1313
<DecodeForm />
14-
1514
<Separator />
16-
1715
<AbiExplorer />
1816
</div>
1917
);

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/InteractForm.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Coins, Eye, Send } from "lucide-react";
44
import { useQueryState } from "nuqs";
5-
import { AbiFunction, stringify } from "viem";
5+
import { AbiFunction, AbiItem, stringify } from "viem";
66
import { useDeferredValue, useMemo } from "react";
77
import { Input } from "../../../../../../components/ui/Input";
88
import { Separator } from "../../../../../../components/ui/Separator";
@@ -12,6 +12,10 @@ import { useHashState } from "../../../../hooks/useHashState";
1212
import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery";
1313
import { FunctionField } from "./FunctionField";
1414

15+
function isFunction(abi: AbiItem): abi is AbiFunction {
16+
return abi.type === "function";
17+
}
18+
1519
export function InteractForm() {
1620
const [hash] = useHashState();
1721
const { data, isFetched } = useWorldAbiQuery();
@@ -20,7 +24,8 @@ export function InteractForm() {
2024
const filteredFunctions = useMemo(() => {
2125
if (!data?.abi) return [];
2226
return data.abi.filter(
23-
(item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
27+
(item): item is AbiFunction =>
28+
isFunction(item) && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
2429
);
2530
}, [data?.abi, deferredFilterValue]);
2631

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ export function TransactionsWatcher() {
8181
receipt,
8282
logs,
8383
value: transaction.value,
84-
status: userOperationEvent?.args.success ? "success" : "reverted",
84+
status:
85+
userOperationEvent && "success" in userOperationEvent.args
86+
? userOperationEvent.args.success
87+
? "success"
88+
: "reverted"
89+
: "reverted",
8590
});
8691
},
8792
[abi, setTransaction, worldAddress],
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Abi, Hex } from "viem";
2+
import { getSystemAbis } from "@latticexyz/store-sync/internal";
3+
import { validateChainId } from "../../../../common";
4+
import { getClient } from "../utils/getClient";
5+
import { getIndexerUrl } from "../utils/getIndexerUrl";
6+
7+
export const dynamic = "force-dynamic";
8+
9+
export type SystemAbisResponse = {
10+
abis: {
11+
[systemId: Hex]: Abi;
12+
};
13+
};
14+
15+
export async function GET(req: Request) {
16+
const { searchParams } = new URL(req.url);
17+
const chainId = Number(searchParams.get("chainId"));
18+
const worldAddress = searchParams.get("worldAddress") as Hex;
19+
const systemIds = searchParams.get("systemIds")?.split(",") as Hex[];
20+
21+
if (!chainId || !worldAddress || !systemIds) {
22+
return Response.json({ error: "Missing chainId, worldAddress or systemIds" }, { status: 400 });
23+
}
24+
validateChainId(chainId);
25+
26+
try {
27+
const client = await getClient(chainId);
28+
const indexerUrl = getIndexerUrl(chainId);
29+
const abis = await getSystemAbis({
30+
client,
31+
worldAddress,
32+
systemIds,
33+
indexerUrl,
34+
chainId,
35+
});
36+
37+
return Response.json({ abis });
38+
} catch (error: unknown) {
39+
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
40+
return Response.json({ error: errorMessage }, { status: 400 });
41+
}
42+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createClient, http } from "viem";
2+
import { chainIdToName, supportedChainId, supportedChains } from "../../../../common";
3+
4+
export async function getClient(chainId: supportedChainId) {
5+
const chain = supportedChains[chainIdToName[chainId]];
6+
const client = createClient({
7+
chain,
8+
transport: http(),
9+
});
10+
11+
return client;
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { chainIdToName, supportedChainId, supportedChains } from "../../../../common";
2+
3+
export function getIndexerUrl(chainId: supportedChainId) {
4+
const chain = supportedChains[chainIdToName[chainId]];
5+
return "indexerUrl" in chain ? chain.indexerUrl : undefined;
6+
}

0 commit comments

Comments
 (0)