From 71eb34804ee9a6c74a10f896f41bf6f74cfc889c Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Fri, 4 Oct 2024 14:26:48 +0300 Subject: [PATCH] feat(explorer): integrate rejected transactions (#3251) --- .changeset/neat-impalas-arrive.md | 5 ++ .../[worldAddress]/observe/Confirmations.tsx | 1 + .../observe/TransactionTableRow.tsx | 46 ++++++++++++------- .../observe/TransactionsTable.tsx | 12 +++-- .../observe/useTransactionWatcher.ts | 30 ++++++++---- packages/explorer/src/observer/decorator.ts | 2 + packages/explorer/src/observer/messages.ts | 6 +-- packages/explorer/src/observer/store.ts | 3 ++ 8 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 .changeset/neat-impalas-arrive.md diff --git a/.changeset/neat-impalas-arrive.md b/.changeset/neat-impalas-arrive.md new file mode 100644 index 0000000000..55829e9925 --- /dev/null +++ b/.changeset/neat-impalas-arrive.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Observe tab is now populated by rejected transactions coming from the `observer` transport wrapper. diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/Confirmations.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/Confirmations.tsx index a3a102e06c..25c91a432e 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/Confirmations.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/Confirmations.tsx @@ -10,6 +10,7 @@ export function Confirmations({ hash }: { hash?: Hex }) { chainId, query: { refetchInterval: 1000, + enabled: !!hash, }, }); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx index 945dbff048..5e874cdebb 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx @@ -9,17 +9,29 @@ import { Confirmations } from "./Confirmations"; import { columns } from "./TransactionsTable"; import { WatchedTransaction } from "./useTransactionWatcher"; -function TranctionTableRowDataCell({ label, children }: { label: string; children: React.ReactNode }) { +function TransactionTableRowDataCell({ + label, + status, + children, +}: { + label: string; + status: WatchedTransaction["status"]; + children: React.ReactNode; +}) { return (

{label}

-

{children ?? }

+

+ {children ?? + (status === "rejected" ? N/A : )} +

); } export function TransactionTableRow({ row }: { row: Row }) { const data = row?.original; + const status = data.status; const logs = data?.logs; const receipt = data?.receipt; @@ -48,25 +60,27 @@ export function TransactionTableRow({ row }: { row: Row }) { {data && ( <>
- - - - - {data.transaction?.value !== undefined ? `${formatEther(data.transaction.value)} ETH` : null} - - {receipt?.gasUsed.toString()} - + + {status !== "rejected" ? : null} + + + {data.value ? formatEther(data.value) : 0} ETH + + + {receipt?.gasUsed.toString()} + + {receipt?.effectiveGasPrice.toString()} - - + + {receipt ? `${formatEther(receipt.gasUsed * receipt.effectiveGasPrice)} ETH` : null} - +
-

Inputs

+

Inputs

{Array.isArray(data.functionData?.args) && data.functionData?.args.length > 0 ? (
{data.functionData?.args?.map((arg, idx) => ( @@ -85,7 +99,7 @@ export function TransactionTableRow({ row }: { row: Row }) { <>
-

Error

+

Error

{data.error ? (
{data.error.message} @@ -100,7 +114,7 @@ export function TransactionTableRow({ row }: { row: Row }) {
-

Logs

+

Logs

{Array.isArray(logs) && logs.length > 0 ? (
    diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsTable.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsTable.tsx index a4319762ff..40a8546b29 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsTable.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsTable.tsx @@ -18,6 +18,9 @@ export const columns = [ columnHelper.accessor("receipt.blockNumber", { header: "Block", cell: (row) => { + const status = row.row.original.status; + if (status === "rejected") return N/A; + const blockNumber = row.getValue(); if (!blockNumber) return ; return ( @@ -28,7 +31,7 @@ export const columns = [ ); }, }), - columnHelper.accessor("transaction.from", { + columnHelper.accessor("from", { header: "From", cell: (row) => { const from = row.getValue(); @@ -49,9 +52,9 @@ export const columns = [ return (
    {functionName} - {status === "pending" && } + {status === "pending" && } {status === "success" && } - {status === "reverted" && } + {(status === "reverted" || status === "rejected") && }
    ); }, @@ -59,6 +62,9 @@ export const columns = [ columnHelper.accessor("hash", { header: "Tx hash", cell: (row) => { + const status = row.row.original.status; + if (status === "rejected") return N/A; + const hash = row.getValue(); if (!hash) return ; return ( diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts index 57b8407103..97c5125d04 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts @@ -1,6 +1,7 @@ import { useParams } from "next/navigation"; import { AbiFunction, + Address, BaseError, DecodeFunctionDataReturnType, Hex, @@ -15,17 +16,21 @@ import { useConfig, useWatchBlocks } from "wagmi"; import { useStore } from "zustand"; import { useCallback, useEffect, useMemo, useState } from "react"; import { getTransaction, simulateContract, waitForTransactionReceipt } from "@wagmi/core"; +import { Message } from "../../../../../../observer/messages"; import { Write, store } from "../../../../../../observer/store"; import { useChain } from "../../../../hooks/useChain"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; export type WatchedTransaction = { hash?: Hex; + writeId?: string; + from?: Address; timestamp?: bigint; transaction?: Transaction; functionData?: DecodeFunctionDataReturnType; + value?: bigint; receipt?: TransactionReceipt; - status: "pending" | "success" | "reverted" | "unknown"; + status: "pending" | "success" | "reverted" | "rejected" | "unknown"; write?: Write; logs?: Log[]; error?: BaseError; @@ -63,6 +68,7 @@ export function useTransactionWatcher() { setTransactions((prevTransactions) => [ { hash, + from: transaction.from, timestamp, transaction, status: "pending", @@ -70,6 +76,7 @@ export function useTransactionWatcher() { functionName, args, }, + value: transaction.value, }, ...prevTransactions, ]); @@ -114,7 +121,7 @@ export function useTransactionWatcher() { receipt, logs, status, - error: transactionError, + error: transactionError as BaseError, } : transaction, ), @@ -147,19 +154,22 @@ export function useTransactionWatcher() { }); const mergedTransactions = useMemo((): WatchedTransaction[] => { - const mergedMap = new Map(); + const mergedMap = new Map(); for (const write of Object.values(observerWrites)) { const parsedAbiItem = parseAbiItem(`function ${write.functionSignature}`) as AbiFunction; - const functionData = { - functionName: parsedAbiItem.name, - args: write.args, - }; + const writeResult = write.events.find((event): event is Message<"write:result"> => event.type === "write:result"); - mergedMap.set(write.hash, { - status: "pending", + mergedMap.set(write.hash || write.writeId, { + from: write.from, + status: writeResult?.status === "rejected" ? "rejected" : "pending", timestamp: BigInt(write.time) / 1000n, - functionData, + functionData: { + functionName: parsedAbiItem.name, + args: write.args, + }, + value: write.value, + error: writeResult && "reason" in writeResult ? (writeResult.reason as BaseError) : undefined, write, }); } diff --git a/packages/explorer/src/observer/decorator.ts b/packages/explorer/src/observer/decorator.ts index ab4c109bf4..f5afcf1f10 100644 --- a/packages/explorer/src/observer/decorator.ts +++ b/packages/explorer/src/observer/decorator.ts @@ -37,8 +37,10 @@ export function observer({ explorerUrl = "http://localhost:13690", waitForTransa emit("write", { writeId, address: args.address, + from: client.account!.address, functionSignature: formatAbiItem(functionAbiItem), args: (args.args ?? []) as never, + value: args.value, }); Promise.allSettled([write]).then(([result]) => { emit("write:result", { ...result, writeId }); diff --git a/packages/explorer/src/observer/messages.ts b/packages/explorer/src/observer/messages.ts index d2d5f5db0f..2526a25fbe 100644 --- a/packages/explorer/src/observer/messages.ts +++ b/packages/explorer/src/observer/messages.ts @@ -6,12 +6,12 @@ export type Messages = { write: { writeId: string; address: Address; + from: Address; functionSignature: string; args: unknown[]; + value?: bigint; }; - "write:result": PromiseSettledResult & { - writeId: string; - }; + "write:result": PromiseSettledResult & { writeId: string }; waitForTransactionReceipt: { writeId: string; hash: Hash; diff --git a/packages/explorer/src/observer/store.ts b/packages/explorer/src/observer/store.ts index 5c4e78b414..19c2196dae 100644 --- a/packages/explorer/src/observer/store.ts +++ b/packages/explorer/src/observer/store.ts @@ -10,11 +10,14 @@ export type Write = { writeId: string; type: MessageType; hash?: Hex; + from?: Address; address: Address; functionSignature: string; args: unknown[]; + value?: bigint; time: number; events: Message>[]; + error?: Error; }; export type State = {