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

feat(explorer): show transactions #3062

Merged
merged 57 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
721598a
add navigation link component
karooolis Aug 23, 2024
1325808
add navigation link component
karooolis Aug 23, 2024
3efe411
monitor all txs, table view
karooolis Aug 23, 2024
643c4e9
add expandable rows
karooolis Aug 23, 2024
f314fd5
show block number, batch add new txs
karooolis Aug 23, 2024
c4290ba
parse txs
karooolis Aug 26, 2024
f66d2e9
txs event and other data (wip)
karooolis Aug 26, 2024
f5e53f8
Merge branch 'main' into kumpis/show-transactions
karooolis Aug 27, 2024
ebd419e
wip (local storage, txs handling, table)
karooolis Aug 27, 2024
5902737
Merge branch 'main' into kumpis/show-transactions
karooolis Aug 27, 2024
d74182f
Merge branch 'main' into kumpis/show-transactions
karooolis Sep 26, 2024
81f9b8a
layout setup
karooolis Sep 26, 2024
785a8df
remove accidentally added vercel project
karooolis Sep 26, 2024
7790e91
wait for tx, handle all states, timestamp, styling
karooolis Sep 26, 2024
70d2cae
add TransactionTableRowDataCell
karooolis Sep 27, 2024
a75287d
use tanstack query for fetching
karooolis Sep 27, 2024
1ed41c7
watch blocks for new txs
karooolis Sep 27, 2024
41f4ad4
fix time ago
karooolis Sep 27, 2024
4f396bc
add block explorer url
karooolis Sep 27, 2024
6a214c8
confirmations, move helper components outside
karooolis Sep 27, 2024
a5570a8
remove ScrollArea
karooolis Sep 27, 2024
c3253de
revert TasksSystem
karooolis Sep 27, 2024
ac94221
update time column
karooolis Sep 27, 2024
c1c0dd0
logs + args display
karooolis Sep 30, 2024
3253c4e
Create blue-starfishes-fry.md
karooolis Sep 30, 2024
5aec20a
Delete packages/explorer/vercel.json
karooolis Sep 30, 2024
bc7c4ec
Merge branch 'main' into kumpis/show-transactions
karooolis Sep 30, 2024
4ae1523
merge observer + block watcher data, and pages ui
karooolis Sep 30, 2024
73ed712
split table into container + view pattern
karooolis Sep 30, 2024
d65f8eb
undo store changes
karooolis Sep 30, 2024
1e05737
update failed status to reverted
karooolis Sep 30, 2024
66fd7c9
derive function data from write event
karooolis Sep 30, 2024
47385e7
ui improvements (title, status, columns order, etc)
karooolis Oct 1, 2024
f463fdf
ui cleanup for logs, args, tighter padding, etc
karooolis Oct 1, 2024
1b1aed2
add gas used + price
karooolis Oct 1, 2024
57dbd9c
show only functions in interact page
karooolis Oct 1, 2024
66846c1
add waiting for txs
karooolis Oct 1, 2024
074bf80
mute time ago cell text color
karooolis Oct 1, 2024
15bd1a3
format tx value
karooolis Oct 1, 2024
6702ff4
add revert reason
karooolis Oct 1, 2024
5386762
update wait for tx view + border styling
karooolis Oct 1, 2024
ee069e4
show bytes for function selector
karooolis Oct 1, 2024
e43901f
undo common change
karooolis Oct 1, 2024
d3147dc
undo observer store change
karooolis Oct 1, 2024
46e01c5
cleanup
karooolis Oct 1, 2024
0444dcf
add hash type to Write
karooolis Oct 1, 2024
0b2c175
add hash to observer write
karooolis Oct 1, 2024
dc7202f
naming cleanup
karooolis Oct 1, 2024
d1f8822
Update .changeset/blue-starfishes-fry.md
karooolis Oct 2, 2024
aec1610
Update packages/explorer/src/app/(explorer)/[chainName]/worlds/[world…
karooolis Oct 2, 2024
758add2
Update packages/explorer/src/app/(explorer)/[chainName]/worlds/[world…
karooolis Oct 2, 2024
676fd51
Update packages/explorer/src/app/(explorer)/[chainName]/worlds/[world…
karooolis Oct 2, 2024
8b052f2
handle missed transactions
karooolis Oct 2, 2024
0cc0c5f
up/down state, expanded styling
karooolis Oct 3, 2024
e6afd91
handle incoming txs from observer, retry receipt fetch
karooolis Oct 3, 2024
25e6841
use waitForTransactionReceipt
karooolis Oct 3, 2024
0e929d2
move handle transaction logic into hook
karooolis Oct 3, 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
5 changes: 5 additions & 0 deletions .changeset/blue-starfishes-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/explorer": patch
---

Observe tab is now populated by transactions flowing through the world, in addition to local transactions when using the `observer` transport wrapper.
Original file line number Diff line number Diff line change
Expand Up @@ -116,38 +116,42 @@ export function TablesViewer({
</div>
)}
{!isLoading && (
<Table>
<TableHeader>
{reactTable.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{reactTable.getRowModel().rows?.length ? (
reactTable.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
<div className="relative w-full overflow-auto">
<Table>
<TableHeader>
{reactTable.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={tableColumns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
))}
</TableHeader>
<TableBody>
{reactTable.getRowModel().rows?.length ? (
reactTable.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={tableColumns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)}
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function Form() {
const { data, isFetched } = useWorldAbiQuery();
const [filterValue, setFilterValue] = useState("");
const deferredFilterValue = useDeferredValue(filterValue);
const filteredFunctions = data?.abi?.filter((item) =>
item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
const filteredFunctions = data?.abi?.filter(
(item) => item.type === "function" && item.name.toLowerCase().includes(deferredFilterValue.toLowerCase()),
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Hex } from "viem";
import { useChain } from "../../../../hooks/useChain";
import { blockExplorerTransactionUrl } from "../../../../utils/blockExplorerTransactionUrl";

export function BlockExplorerLink({ hash, children }: { hash?: Hex; children: React.ReactNode }) {
const { id: chainId } = useChain();
const url = blockExplorerTransactionUrl({ chainId, hash });

if (!url) return children;
return (
<a href={url} target="_blank" rel="noopener noreferrer" className="flex hover:underline">
{children}
</a>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Hex } from "viem";
import { useTransactionConfirmations } from "wagmi";
import { Skeleton } from "../../../../../../components/ui/Skeleton";
import { useChain } from "../../../../hooks/useChain";

export function Confirmations({ hash }: { hash?: Hex }) {
const { id: chainId } = useChain();
const { data: confirmations } = useTransactionConfirmations({
hash,
chainId,
query: {
refetchInterval: 1000,
},
});

if (!confirmations) return <Skeleton className="h-4 w-[50px]" />;
return (
<span className="flex items-center text-xs font-extrabold text-green-600">
<span className="mr-2 inline-block h-2 w-2 animate-pulse rounded-full bg-success"></span>
<span className="opacity-70">{confirmations.toString()}</span>
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useEffect, useState } from "react";
import { timeAgo } from "../../../../utils/timeAgo";

export function TimeAgo({ timestamp }: { timestamp: bigint }) {
const [ago, setAgo] = useState(() => timeAgo(timestamp));

useEffect(() => {
const timer = setInterval(() => {
setAgo(timeAgo(timestamp));
}, 1000);

return () => clearInterval(timer);
}, [timestamp]);

return (
<span className="text-white/60" title={new Date(Number(timestamp) * 1000).toISOString()}>
{ago}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { formatEther } from "viem";
import { Row, flexRender } from "@tanstack/react-table";
import { Separator } from "../../../../../../components/ui/Separator";
import { Skeleton } from "../../../../../../components/ui/Skeleton";
import { TableCell, TableRow } from "../../../../../../components/ui/Table";
import { cn } from "../../../../../../utils";
import { Confirmations } from "./Confirmations";
import { WatchedTransaction } from "./TransactionsTableContainer";
import { columns } from "./TransactionsTableView";

function TranctionTableRowDataCell({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div>
<h3 className="text-2xs font-bold uppercase text-white/60">{label}</h3>
<p className="pt-1 text-xs uppercase">{children ?? <Skeleton className="h-4 w-[100px]" />}</p>
</div>
);
}

export function TransactionTableRow({ row }: { row: Row<WatchedTransaction> }) {
const data = row?.original;
const logs = data?.logs;
const receipt = data?.receipt;

return (
<>
<TableRow
className={cn("relative cursor-pointer", {
"bg-muted/50": row.getIsExpanded(),
})}
onClick={() => row.toggleExpanded()}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}

{row.getIsExpanded() ? (
<ChevronUpIcon className="absolute right-4 top-1/2 h-4 w-4 -translate-y-1/2 text-white/60" />
) : (
<ChevronDownIcon className="absolute right-4 top-1/2 h-4 w-4 -translate-y-1/2 text-white/60" />
)}
</TableRow>

{row.getIsExpanded() && (
<TableRow className="border-b-white/20 bg-muted/50 hover:bg-muted/50">
<TableCell colSpan={columns.length}>
{data && (
<>
<div className="grid grid-cols-2 gap-x-2 gap-y-5 sm:grid-cols-4 md:grid-cols-5">
<TranctionTableRowDataCell label="Confirmations">
<Confirmations hash={data.transaction?.hash} />
</TranctionTableRowDataCell>
<TranctionTableRowDataCell label="Tx value">
{data.transaction?.value !== undefined ? `${formatEther(data.transaction.value)} ETH` : null}
</TranctionTableRowDataCell>
<TranctionTableRowDataCell label="Gas used">{receipt?.gasUsed.toString()}</TranctionTableRowDataCell>
<TranctionTableRowDataCell label="Gas price">
{receipt?.effectiveGasPrice.toString()}
</TranctionTableRowDataCell>
<TranctionTableRowDataCell label="Tx cost">
{receipt ? `${formatEther(receipt.gasUsed * receipt.effectiveGasPrice)} ETH` : null}
</TranctionTableRowDataCell>
</div>

<Separator className="my-5" />

<div className="flex items-start gap-x-4">
<h3 className="w-[45px] text-2xs font-bold uppercase">Inputs</h3>
{Array.isArray(data.functionData?.args) && data.functionData?.args.length > 0 ? (
<div className="flex-grow border border-white/20 p-2">
{data.functionData?.args?.map((arg, idx) => (
<div key={idx} className="flex">
<span className="flex-shrink-0 text-xs text-white/60">arg {idx + 1}:</span>
<span className="ml-2 text-xs">{String(arg)}</span>
</div>
))}
</div>
) : (
<p className="text-2xs uppercase text-white/60">No inputs</p>
)}
</div>

{data.error ? (
<>
<Separator className="my-5" />
<div className="flex items-start gap-x-4">
<h3 className="w-[45px] text-2xs font-bold uppercase">Error</h3>
{data.error ? (
<div className="flex-grow whitespace-pre-wrap border border-red-500 p-2 font-mono text-xs">
{data.error.message}
</div>
) : (
<p className="text-2xs uppercase text-white/60">No error</p>
)}
</div>
</>
) : null}

<Separator className="my-5" />

<div className="flex items-start gap-x-4 pb-2">
<h3 className="inline-block w-[45px] pb-2 text-2xs font-bold uppercase">Logs</h3>
{Array.isArray(logs) && logs.length > 0 ? (
<div className="flex-grow break-all border border-white/20 p-2 pb-3">
<ul>
{logs.map((log, idx) => {
const eventName = "eventName" in log ? log.eventName : null;
const args = "args" in log ? (log.args as Record<string, unknown>) : null;
return (
<li key={idx}>
{Boolean(eventName) && <span className="text-xs">{eventName?.toString()}:</span>}
{args && (
<ul className="list-inside">
{Object.entries(args).map(([key, value]) => (
<li key={key} className="mt-1 flex">
<span className="flex-shrink-0 text-xs text-white/60">{key}: </span>
<span className="ml-2 text-xs">{value as never}</span>
</li>
))}
</ul>
)}
{idx < logs.length - 1 && <Separator className="my-4" />}
</li>
);
})}
</ul>
</div>
) : (
<p className="text-2xs uppercase text-white/60">No logs</p>
)}
</div>
</>
)}
</TableCell>
</TableRow>
)}
</>
);
}
Loading
Loading