-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(explorer): show transactions (#3062)
Co-authored-by: Kevin Ingersoll <[email protected]>
- Loading branch information
Showing
24 changed files
with
675 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...plorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/BlockExplorerLink.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
23 changes: 23 additions & 0 deletions
23
...s/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/Confirmations.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TimeAgo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
140 changes: 140 additions & 0 deletions
140
...orer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { columns } from "./TransactionsTable"; | ||
import { WatchedTransaction } from "./useTransactionWatcher"; | ||
|
||
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> | ||
)} | ||
</> | ||
); | ||
} |
Oops, something went wrong.