Skip to content

Commit 463ced5

Browse files
committed
add block height param
1 parent c9a7e15 commit 463ced5

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

packages/explorer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"@radix-ui/react-popover": "^1.1.1",
5555
"@radix-ui/react-select": "^2.1.1",
5656
"@radix-ui/react-separator": "^1.1.0",
57+
"@radix-ui/react-slider": "^1.3.5",
5758
"@radix-ui/react-slot": "^1.1.0",
5859
"@radix-ui/react-tooltip": "^1.1.6",
5960
"@radix-ui/themes": "^3.0.5",

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
"use client";
22

3-
import { CommandIcon, CornerDownLeft, LoaderIcon, PauseIcon, PlayIcon } from "lucide-react";
3+
import { ClockIcon, CommandIcon, CornerDownLeft, LoaderIcon, PauseIcon, PlayIcon } from "lucide-react";
44
import { KeyCode, KeyMod, editor } from "monaco-editor/esm/vs/editor/editor.api";
5+
import { useQueryState } from "nuqs";
6+
import { useConfig } from "wagmi";
7+
import { getBlock } from "wagmi/actions";
58
import { useEffect, useRef, useState } from "react";
69
import { useForm } from "react-hook-form";
710
import { Table } from "@latticexyz/config";
811
import Editor from "@monaco-editor/react";
912
import { Tooltip } from "../../../../../../components/Tooltip";
1013
import { Button } from "../../../../../../components/ui/Button";
1114
import { Form, FormField } from "../../../../../../components/ui/Form";
15+
import { Input } from "../../../../../../components/ui/Input";
1216
import { cn } from "../../../../../../utils";
17+
import { useChain } from "../../../../hooks/useChain";
1318
import { useTableDataQuery } from "../../../../queries/useTableDataQuery";
1419
import { PAGE_SIZE_OPTIONS, monacoOptions } from "./consts";
1520
import { usePaginationState } from "./hooks/usePaginationState";
@@ -31,6 +36,12 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
3136
const [isUserTriggeredRefetch, setIsUserTriggeredRefetch] = useState(false);
3237
const [pagination, setPagination] = usePaginationState();
3338
const [query, setQuery] = useSQLQueryState();
39+
const [blockHeight, setBlockHeight] = useQueryState("blockHeight");
40+
const [blockTimestamp, setBlockTimestamp] = useState<number | null>(null);
41+
const [isLoadingBlock, setIsLoadingBlock] = useState(false);
42+
43+
const wagmiConfig = useConfig();
44+
const { id: chainId } = useChain();
3445

3546
const validateQuery = useQueryValidator(table);
3647
const { data: tableData, refetch, isRefetching: isTableDataRefetching } = useTableDataQuery({ table, isLiveQuery });
@@ -76,6 +87,32 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
7687
form.reset({ query });
7788
}, [query, form]);
7889

90+
// Fetch block timestamp when blockHeight changes
91+
useEffect(() => {
92+
const fetchBlockTimestamp = async () => {
93+
if (!blockHeight || !wagmiConfig || !chainId) {
94+
setBlockTimestamp(null);
95+
return;
96+
}
97+
98+
setIsLoadingBlock(true);
99+
try {
100+
const block = await getBlock(wagmiConfig, {
101+
chainId,
102+
blockNumber: BigInt(blockHeight),
103+
});
104+
setBlockTimestamp(Number(block.timestamp));
105+
} catch (error) {
106+
console.error("Failed to fetch block timestamp:", error);
107+
setBlockTimestamp(null);
108+
} finally {
109+
setIsLoadingBlock(false);
110+
}
111+
};
112+
113+
fetchBlockTimestamp();
114+
}, [blockHeight, wagmiConfig, chainId]);
115+
79116
const updateHeight = () => {
80117
if (editorRef.current) {
81118
const contentHeight = Math.min(200, editorRef.current.getContentHeight());
@@ -182,6 +219,29 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
182219
</>
183220
) : null}
184221

222+
<Input
223+
type="number"
224+
step="1000"
225+
className="w-[120px]"
226+
value={blockHeight ?? ""}
227+
onChange={(e) => setBlockHeight(e.target.value)}
228+
/>
229+
230+
{blockHeight && (
231+
<div className="flex items-center gap-2 text-xs text-white/60">
232+
<ClockIcon className="h-3 w-3" />
233+
{isLoadingBlock ? (
234+
<span>Loading...</span>
235+
) : blockTimestamp ? (
236+
<Tooltip text={new Date(blockTimestamp * 1000).toISOString()}>
237+
<span>{new Date(blockTimestamp * 1000).toLocaleString()}</span>
238+
</Tooltip>
239+
) : (
240+
<span>Invalid block</span>
241+
)}
242+
</div>
243+
)}
244+
185245
<Button className="group relative flex gap-2 pl-4 pr-3" type="submit" disabled={isRefetching}>
186246
Run
187247
<span className="relative flex items-center gap-0.5 text-white/60">

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/content/encodeFunctionArgs.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,30 @@ export function encodeFunctionArgs(args: unknown[], inputs: AbiFunction): unknow
88
if (input.type.includes("[]")) return JSON.parse(arg as string);
99
if (input.type === "tuple") return JSON.parse(arg as string);
1010
if (input.type === "bool") return arg === "true";
11+
1112
if (input.type.startsWith("uint") || input.type.startsWith("int")) {
12-
if (typeof arg === "string" && arg.endsWith("n")) return arg.slice(0, -1);
13-
return arg;
13+
const argStr = String(arg).trim();
14+
15+
// Handle values ending with "n" (BigInt notation)
16+
if (argStr.endsWith("n")) {
17+
const numericPart = argStr.slice(0, -1);
18+
return BigInt(numericPart);
19+
}
20+
21+
// Handle regular numeric strings
22+
if (argStr === "") return 0n;
23+
24+
// Remove any non-numeric characters except minus sign and decimal point
25+
const cleanArg = argStr.replace(/[^\d.-]/g, "");
26+
if (cleanArg === "" || cleanArg === "-" || cleanArg === ".") return 0n;
27+
28+
// Handle decimal numbers by truncating to integer
29+
if (cleanArg.includes(".")) {
30+
const [integerPart] = cleanArg.split(".");
31+
return BigInt(integerPart || "0");
32+
}
33+
34+
return BigInt(cleanArg);
1435
}
1536

1637
return arg;

packages/explorer/src/app/(explorer)/queries/useTableDataQuery.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useParams } from "next/navigation";
2+
import { useQueryState } from "nuqs";
23
import { Hex, stringify } from "viem";
34
import { Table } from "@latticexyz/config";
45
import { useQuery } from "@tanstack/react-query";
@@ -24,6 +25,7 @@ export function useTableDataQuery({ table, isLiveQuery }: Props) {
2425
const { chainName, worldAddress } = useParams();
2526
const { id: chainId } = useChain();
2627
const [query] = useSQLQueryState();
28+
const [blockHeightParm] = useQueryState("blockHeight");
2729
const indexer = useIndexerForChainId(chainId);
2830

2931
return useQuery<DozerResponse & { queryDuration: number; blockHeight: number }, Error, TData | undefined>({
@@ -39,6 +41,7 @@ export function useTableDataQuery({ table, isLiveQuery }: Props) {
3941
{
4042
address: worldAddress as Hex,
4143
query,
44+
...(blockHeightParm ? { block_height: parseInt(blockHeightParm), block_height_direction: "<=" } : {}),
4245
},
4346
]),
4447
});

packages/explorer/src/components/LatestBlock.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function LatestBlock() {
88
watch: true,
99
chainId,
1010
query: {
11-
refetchInterval: 1000,
11+
refetchInterval: 2_000,
1212
},
1313
});
1414

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as SliderPrimitive from "@radix-ui/react-slider";
5+
import { cn } from "../../utils";
6+
7+
const Slider = React.forwardRef<
8+
React.ElementRef<typeof SliderPrimitive.Root>,
9+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
10+
>(({ className, ...props }, ref) => (
11+
<SliderPrimitive.Root
12+
ref={ref}
13+
className={cn("relative flex w-full touch-none select-none items-center", className)}
14+
{...props}
15+
>
16+
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
17+
<SliderPrimitive.Range className="absolute h-full bg-primary" />
18+
</SliderPrimitive.Track>
19+
{/* // TODO: adjust later */}
20+
{/* eslint-disable-next-line max-len */}
21+
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
22+
</SliderPrimitive.Root>
23+
));
24+
Slider.displayName = SliderPrimitive.Root.displayName;
25+
26+
export { Slider };

0 commit comments

Comments
 (0)