Skip to content

Commit

Permalink
handle missed transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
karooolis committed Oct 2, 2024
1 parent 676fd51 commit 8b052f2
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 130 deletions.
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
@@ -1,17 +1,15 @@
import { ExternalLinkIcon } from "lucide-react";
import { Hex } from "viem";
import { useChain } from "../../../../hooks/useChain";
import { explorerForChainId } from "../../../../utils/explorerForChainId";
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 (!explorerUrl) return children;
if (!url) return children;
return (
<a href={`${explorerUrl}/tx/${hash}`} target="_blank" rel="noopener noreferrer" className="flex items-center hover:underline">
<ExternalLinkIcon className="mr-2 h-3 w-3" /> {children}
</div>
<a href={url} target="_blank" rel="noopener noreferrer" className="flex hover:underline">
{children}
</a>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ export function Confirmations({ hash }: { hash?: Hex }) {
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"
style={{
background: "rgb(64, 182, 107)",
}}
></span>
<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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function TransactionTableRow({ row }: { row: Row<WatchedTransaction> }) {
<TableCell colSpan={columns.length}>
{data && (
<>
<div className="grid grid-cols-2 gap-5 sm:grid-cols-4">
<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>
Expand All @@ -45,6 +45,11 @@ export function TransactionTableRow({ row }: { row: Row<WatchedTransaction> }) {
<TranctionTableRowDataCell label="Gas price">
{receipt?.effectiveGasPrice.toString()}
</TranctionTableRowDataCell>
<TranctionTableRowDataCell label="Tx cost">
{receipt?.gasUsed && receipt?.effectiveGasPrice
? `${formatEther(receipt.gasUsed * receipt.effectiveGasPrice)} ETH`
: null}
</TranctionTableRowDataCell>
</div>

<Separator className="my-5" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "viem";
import { useConfig, useWatchBlocks } from "wagmi";
import { useStore } from "zustand";
import React, { useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { getTransaction, getTransactionReceipt, simulateContract } from "@wagmi/core";
import { Write, store } from "../../../../../../observer/store";
import { useChain } from "../../../../hooks/useChain";
Expand Down Expand Up @@ -55,6 +55,7 @@ export function TransactionsTableContainer() {

mergedMap.set(write.hash, {
status: "pending",
timestamp: BigInt(write.time) / 1000n,
functionData,
write,
});
Expand All @@ -69,70 +70,94 @@ export function TransactionsTableContainer() {
}
}

return Array.from(mergedMap.values()).reverse();
return Array.from(mergedMap.values()).sort((a, b) => Number(b.timestamp ?? 0n) - Number(a.timestamp ?? 0n));
}, [transactions, observerWrites]);

async function handleTransaction(hash: Hex, timestamp: bigint) {
if (!abi) return;
const handleTransaction = useCallback(
async (hash: Hex, timestamp: bigint) => {
if (!abi) return;

const transaction = await getTransaction(wagmiConfig, { hash });
if (transaction.to !== worldAddress) return;
const transaction = await getTransaction(wagmiConfig, { hash });
if (transaction.to !== worldAddress) return;

const receipt = await getTransactionReceipt(wagmiConfig, { hash });
setTransactions((prevTransactions) => [
{
hash,
timestamp,
transaction,
status: "pending",
},
...prevTransactions,
]);

let functionName: string | undefined;
let args: readonly unknown[] | undefined;
let transactionError: BaseError | undefined;
try {
const functionData = decodeFunctionData({ abi, data: transaction.input });
functionName = functionData.functionName;
args = functionData.args;
} catch (error) {
transactionError = error as BaseError;
functionName = transaction.input.length > 10 ? transaction.input.slice(0, 10) : "unknown";
}
let functionName: string | undefined;
let args: readonly unknown[] | undefined;
let transactionError: BaseError | undefined;

if (receipt.status === "reverted" && functionName) {
try {
// Simulate the failed transaction to retrieve the revert reason
// Note, it only works for functions that are declared in the ABI
// See: https://github.com/wevm/viem/discussions/462
await simulateContract(wagmiConfig, {
account: transaction.from,
address: worldAddress,
abi,
value: transaction.value,
blockNumber: receipt.blockNumber,
functionName,
args,
});
const functionData = decodeFunctionData({ abi, data: transaction.input });
functionName = functionData.functionName;
args = functionData.args;
} catch (error) {
transactionError = error as BaseError;
functionName = transaction.input.length > 10 ? transaction.input.slice(0, 10) : "unknown";
}
}

const logs = parseEventLogs({
abi,
logs: receipt.logs,
});

setTransactions((prevTransactions) => [
{
hash,
transaction,
functionData: {
functionName,
args,
},
receipt,
logs,
timestamp,
status: receipt.status,
error: transactionError,
},
...prevTransactions,
]);
}
setTransactions((prevTransactions) =>
prevTransactions.map((transaction) =>
transaction.hash === hash
? {
...transaction,
functionData: {
functionName,
args,
},
}
: transaction,
),
);

const receipt = await getTransactionReceipt(wagmiConfig, { hash });
if (receipt.status === "reverted" && functionName) {
try {
// Simulate the failed transaction to retrieve the revert reason
// Note, it only works for functions that are declared in the ABI
// See: https://github.com/wevm/viem/discussions/462
await simulateContract(wagmiConfig, {
account: transaction.from,
address: worldAddress,
abi,
value: transaction.value,
blockNumber: receipt.blockNumber,
functionName,
args,
});
} catch (error) {
transactionError = error as BaseError;
}
}

const logs = parseEventLogs({
abi,
logs: receipt.logs,
});

setTransactions((prevTransactions) =>
prevTransactions.map((transaction) =>
transaction.hash === hash
? {
...transaction,
receipt,
logs,
status: receipt.status,
error: transactionError,
}
: transaction,
),
);
},
[abi, wagmiConfig, worldAddress],
);

useWatchBlocks({
onBlock(block) {
Expand All @@ -145,5 +170,22 @@ export function TransactionsTableContainer() {
pollingInterval: 500,
});

useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden) {
for (const write of observerWrites) {
if (write.hash && !transactions.find((t) => t.hash === write.hash)) {
handleTransaction(write.hash, BigInt(write.time) / 1000n);
}
}
}
};

document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [handleTransaction, observerWrites, transactions]);

return <TransactionsTableView data={mergedTransactions} />;
}
Loading

0 comments on commit 8b052f2

Please sign in to comment.