From 624cf9aa7b832e41182192c108184fa637f034a4 Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 13:59:14 +0300 Subject: [PATCH 01/15] move txs to global store --- .../worlds/[worldAddress]/Providers.tsx | 5 +- .../observe/TransactionTableRow.tsx | 2 +- .../observe/TransactionsTable.tsx | 4 +- ...tionWatcher.ts => TransactionsWatcher.tsx} | 144 ++++++------------ .../observe/useTransactionsWatcher.ts | 77 ++++++++++ .../store/WorldStoreProvider.tsx | 26 ++++ .../[worldAddress]/store/createWorldStore.ts | 24 +++ .../worlds/[worldAddress]/store/index.ts | 3 + .../[worldAddress]/store/useWorldStore.ts | 13 ++ .../explorer/src/app/(explorer)/layout.tsx | 4 +- 10 files changed, 195 insertions(+), 107 deletions(-) rename packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/{useTransactionWatcher.ts => TransactionsWatcher.tsx} (51%) create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx index 614b38186c..07694ae414 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx @@ -8,6 +8,7 @@ import "@rainbow-me/rainbowkit/styles.css"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { getDefaultAnvilConnectors } from "../../../../../connectors/anvil"; import { useChain } from "../../../hooks/useChain"; +import { WorldStoreProvider } from "./store"; const queryClient = new QueryClient(); @@ -36,7 +37,9 @@ export function Providers({ children }: { children: ReactNode }) { return ( - {children} + + {children} + ); 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 9b7d4af1ef..cc2a96a67c 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 @@ -8,7 +8,7 @@ import { cn } from "../../../../../../utils"; import { Confirmations } from "./Confirmations"; import { TimingRowExpanded } from "./TimingRowExpanded"; import { columns } from "./TransactionsTable"; -import { WatchedTransaction } from "./useTransactionWatcher"; +import { WatchedTransaction } from "./useTransactionsWatcher"; function TransactionTableRowDataCell({ label, 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 c3b5eacea0..5e903197a6 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 @@ -12,7 +12,7 @@ import { BlockExplorerLink } from "./BlockExplorerLink"; import { TimeAgo } from "./TimeAgo"; import { TimingRowHeader } from "./TimingRowHeader"; import { TransactionTableRow } from "./TransactionTableRow"; -import { WatchedTransaction, useTransactionWatcher } from "./useTransactionWatcher"; +import { WatchedTransaction, useTransactionsWatcher } from "./useTransactionsWatcher"; const columnHelper = createColumnHelper(); export const columns = [ @@ -94,7 +94,7 @@ export const columns = [ ]; export function TransactionsTable() { - const transactions = useTransactionWatcher(); + const transactions = useTransactionsWatcher(); const [expanded, setExpanded] = useState({}); const table = useReactTable({ diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx similarity index 51% rename from packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts rename to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index 58d56c2a9e..fc8c542c23 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionWatcher.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -1,48 +1,26 @@ -import { useParams } from "next/navigation"; -import { - AbiFunction, - Address, - BaseError, - DecodeFunctionDataReturnType, - Hex, - Log, - Transaction, - TransactionReceipt, - decodeFunctionData, - parseAbiItem, - parseEventLogs, -} from "viem"; +import { useParams, usePathname } from "next/navigation"; +import { toast } from "sonner"; +import { BaseError, Hex, TransactionReceipt, decodeFunctionData, parseEventLogs } from "viem"; import { useConfig, useWatchBlocks } from "wagmi"; import { getTransaction, simulateContract, waitForTransactionReceipt } from "wagmi/actions"; import { useStore } from "zustand"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Message } from "../../../../../../observer/messages"; -import { type Write, store } from "../../../../../../observer/store"; +import { ReactNode } from "react"; +import { useCallback, useEffect } from "react"; +import { store } from "../../../../../../observer/store"; import { useChain } from "../../../../hooks/useChain"; +import { useWorldUrl } from "../../../../hooks/useWorldUrl"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; +import { useWorldStore } from "../store/useWorldStore"; -export type WatchedTransaction = { - writeId: string; - hash?: Hex; - from?: Address; - timestamp?: bigint; - transaction?: Transaction; - functionData?: DecodeFunctionDataReturnType; - value?: bigint; - receipt?: TransactionReceipt; - status: "pending" | "success" | "reverted" | "rejected" | "unknown"; - write?: Write; - logs?: Log[]; - error?: BaseError; -}; - -export function useTransactionWatcher() { +export function TransactionsWatcher({ children }: { children: ReactNode }) { + const getLinkUrl = useWorldUrl(); + const pathname = usePathname(); const { id: chainId } = useChain(); const { worldAddress } = useParams(); const wagmiConfig = useConfig(); const { data: worldAbiData } = useWorldAbiQuery(); const abi = worldAbiData?.abi; - const [transactions, setTransactions] = useState([]); + const { transactions, setTransaction, updateTransaction } = useWorldStore(); const observerWrites = useStore(store, (state) => state.writes); const handleTransaction = useCallback( @@ -65,23 +43,31 @@ export function useTransactionWatcher() { functionName = transaction.input.length > 10 ? transaction.input.slice(0, 10) : "unknown"; } - const write = Object.values(observerWrites).find((write) => write.hash === hash); - setTransactions((prevTransactions) => [ - { - hash, - writeId: write?.writeId ?? hash, - from: transaction.from, - timestamp, - transaction, - status: "pending", - functionData: { - functionName, - args, + if (pathname !== getLinkUrl("observe")) { + // TODO: submit toast with new tx + toast(`${functionName}(${args?.join(", ")})`, { + description: `Tx hash: ${hash}`, + action: { + label: "Open", + onClick: () => console.log("Open"), }, - value: transaction.value, + }); + } + + const write = Object.values(observerWrites).find((write) => write.hash === hash); + setTransaction({ + hash, + writeId: write?.writeId ?? hash, + from: transaction.from, + timestamp, + transaction, + status: "pending", + functionData: { + functionName, + args, }, - ...prevTransactions, - ]); + value: transaction.value, + }); let receipt: TransactionReceipt | undefined; try { @@ -115,21 +101,14 @@ export function useTransactionWatcher() { logs: receipt?.logs || [], }); - setTransactions((prevTransactions) => - prevTransactions.map((transaction) => - transaction.hash === hash - ? { - ...transaction, - receipt, - logs, - status, - error: transactionError as BaseError, - } - : transaction, - ), - ); + updateTransaction(hash, { + receipt, + logs, + status, + error: transactionError as BaseError, + }); }, - [abi, observerWrites, wagmiConfig, worldAddress], + [abi, wagmiConfig, worldAddress, pathname, getLinkUrl, observerWrites, setTransaction, updateTransaction], ); useEffect(() => { @@ -155,42 +134,5 @@ export function useTransactionWatcher() { pollingInterval: 500, }); - const mergedTransactions = useMemo((): WatchedTransaction[] => { - const mergedMap = new Map(); - - for (const write of Object.values(observerWrites)) { - if (write.address !== worldAddress) continue; - - const parsedAbiItem = parseAbiItem(`function ${write.functionSignature}`) as AbiFunction; - const writeResult = write.events.find((event): event is Message<"write:result"> => event.type === "write:result"); - - mergedMap.set(write.hash || write.writeId, { - hash: write.hash, - writeId: write.writeId, - from: write.from, - status: writeResult?.status === "rejected" ? "rejected" : "pending", - timestamp: BigInt(write.time) / 1000n, - functionData: { - functionName: parsedAbiItem.name, - args: write.args, - }, - value: write.value, - error: writeResult && "reason" in writeResult ? (writeResult.reason as BaseError) : undefined, - write, - }); - } - - for (const transaction of transactions) { - const existing = mergedMap.get(transaction.hash); - if (existing) { - mergedMap.set(transaction.hash, { ...transaction, write: existing.write }); - } else { - mergedMap.set(transaction.hash, { ...transaction }); - } - } - - return Array.from(mergedMap.values()).sort((a, b) => Number(b.timestamp ?? 0n) - Number(a.timestamp ?? 0n)); - }, [observerWrites, worldAddress, transactions]); - - return mergedTransactions; + return children; } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts new file mode 100644 index 0000000000..749919907d --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts @@ -0,0 +1,77 @@ +import { useParams } from "next/navigation"; +import { + AbiFunction, + Address, + BaseError, + DecodeFunctionDataReturnType, + Hex, + Log, + Transaction, + TransactionReceipt, + parseAbiItem, +} from "viem"; +import { useStore } from "zustand"; +import { useMemo } from "react"; +import { Message } from "../../../../../../observer/messages"; +import { type Write, store } from "../../../../../../observer/store"; +import { useWorldStore } from "../store/useWorldStore"; + +export type WatchedTransaction = { + writeId: string; + hash?: Hex; + from?: Address; + timestamp?: bigint; + transaction?: Transaction; + functionData?: DecodeFunctionDataReturnType; + value?: bigint; + receipt?: TransactionReceipt; + status: "pending" | "success" | "reverted" | "rejected" | "unknown"; + write?: Write; + logs?: Log[]; + error?: BaseError; +}; + +export function useTransactionsWatcher() { + const { worldAddress } = useParams(); + const { transactions } = useWorldStore(); + const observerWrites = useStore(store, (state) => state.writes); + + const mergedTransactions = useMemo((): WatchedTransaction[] => { + const mergedMap = new Map(); + + for (const write of Object.values(observerWrites)) { + if (write.address !== worldAddress) continue; + + const parsedAbiItem = parseAbiItem(`function ${write.functionSignature}`) as AbiFunction; + const writeResult = write.events.find((event): event is Message<"write:result"> => event.type === "write:result"); + + mergedMap.set(write.hash || write.writeId, { + hash: write.hash, + writeId: write.writeId, + from: write.from, + status: writeResult?.status === "rejected" ? "rejected" : "pending", + timestamp: BigInt(write.time) / 1000n, + functionData: { + functionName: parsedAbiItem.name, + args: write.args, + }, + value: write.value, + error: writeResult && "reason" in writeResult ? (writeResult.reason as BaseError) : undefined, + write, + }); + } + + for (const transaction of transactions) { + const existing = mergedMap.get(transaction.hash); + if (existing) { + mergedMap.set(transaction.hash, { ...transaction, write: existing.write }); + } else { + mergedMap.set(transaction.hash, { ...transaction }); + } + } + + return Array.from(mergedMap.values()).sort((a, b) => Number(b.timestamp ?? 0n) - Number(a.timestamp ?? 0n)); + }, [observerWrites, worldAddress, transactions]); + + return mergedTransactions; +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx new file mode 100644 index 0000000000..aed7700633 --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { type ReactNode, createContext, useRef } from "react"; +import { TransactionsWatcher } from "../observe/TransactionsWatcher"; +import { createWorldStore } from "./createWorldStore"; + +export type WorldStore = ReturnType; + +export const WorldStoreContext = createContext(undefined); + +type Props = { + children: ReactNode; +}; + +export const WorldStoreProvider = ({ children }: Props) => { + const storeRef = useRef(); + if (!storeRef.current) { + storeRef.current = createWorldStore(); + } + + return ( + + {children} + + ); +}; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts new file mode 100644 index 0000000000..60cc3c543f --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts @@ -0,0 +1,24 @@ +import { createStore } from "zustand"; +import { WatchedTransaction } from "../observe/useTransactionsWatcher"; + +export type AppStoreData = { + transactions: WatchedTransaction[]; + setTransaction: (transaction: WatchedTransaction) => void; + updateTransaction: (hash: string, updatedTransaction: Partial) => void; +}; + +export const createWorldStore = () => { + return createStore()((set) => ({ + transactions: [], + + setTransaction: (transaction) => + set((state) => ({ + transactions: [...state.transactions, transaction], + })), + + updateTransaction: (hash: string, updatedTransaction: Partial) => + set((state) => ({ + transactions: state.transactions.map((tx) => (tx.hash === hash ? { ...tx, ...updatedTransaction } : tx)), + })), + })); +}; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts new file mode 100644 index 0000000000..cbbcc1d675 --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts @@ -0,0 +1,3 @@ +export * from "./createWorldStore"; +export * from "./useWorldStore"; +export * from "./WorldStoreProvider"; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts new file mode 100644 index 0000000000..ed28605170 --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts @@ -0,0 +1,13 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import { WorldStoreContext } from "./WorldStoreProvider"; + +export const useWorldStore = () => { + const worldStoreContext = useContext(WorldStoreContext); + + if (!worldStoreContext) { + throw new Error(`useWorldStore must be used within WorldStoreProvider`); + } + + return useStore(worldStoreContext); +}; diff --git a/packages/explorer/src/app/(explorer)/layout.tsx b/packages/explorer/src/app/(explorer)/layout.tsx index 3ee4268150..149a752f55 100644 --- a/packages/explorer/src/app/(explorer)/layout.tsx +++ b/packages/explorer/src/app/(explorer)/layout.tsx @@ -29,9 +29,9 @@ export default function RootLayout({ return ( - +
{children}
- +
From 68328137d66fe90008a94524e4bbd6115d85b9ca Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 14:42:30 +0300 Subject: [PATCH 02/15] manage toasts --- .../explore/EditableTableCell.tsx | 15 +++------ .../observe/TransactionsWatcher.tsx | 31 +++++++++++++------ .../[worldAddress]/store/createWorldStore.ts | 2 -- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx index 921add083e..a67f9c1ff1 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx @@ -58,14 +58,11 @@ export function EditableTableCell({ name, table, keyTuple, value: defaultValue } return { txHash, receipt }; }, - onMutate: () => { - const toastId = toast.loading("Transaction submitted"); - return { toastId }; - }, - onSuccess: ({ txHash }, newValue, { toastId }) => { + onSuccess: ({ txHash }, newValue) => { setValue(newValue); + toast.success(`Transaction successful with hash: ${txHash}`, { - id: toastId, + id: txHash, }); queryClient.invalidateQueries({ queryKey: [ @@ -77,11 +74,9 @@ export function EditableTableCell({ name, table, keyTuple, value: defaultValue } ], }); }, - onError: (error, _, context) => { + onError: (error) => { console.error("Error:", error); - toast.error(error.message || "Something went wrong. Please try again.", { - id: context?.toastId, - }); + toast.error(error.message || "Something went wrong. Please try again."); setValue(defaultValue); }, }); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index fc8c542c23..3a3f744902 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -1,5 +1,5 @@ import { useParams, usePathname } from "next/navigation"; -import { toast } from "sonner"; +import { toast, useSonner } from "sonner"; import { BaseError, Hex, TransactionReceipt, decodeFunctionData, parseEventLogs } from "viem"; import { useConfig, useWatchBlocks } from "wagmi"; import { getTransaction, simulateContract, waitForTransactionReceipt } from "wagmi/actions"; @@ -12,6 +12,13 @@ import { useWorldUrl } from "../../../../hooks/useWorldUrl"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; import { useWorldStore } from "../store/useWorldStore"; +const formatHexInput = (input: unknown): string => { + if (typeof input === "string" && input.startsWith("0x")) { + return input.length > 10 ? `${input.slice(0, 6)}...${input.slice(-4)}` : input; + } + return String(input); +}; + export function TransactionsWatcher({ children }: { children: ReactNode }) { const getLinkUrl = useWorldUrl(); const pathname = usePathname(); @@ -22,6 +29,7 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { const abi = worldAbiData?.abi; const { transactions, setTransaction, updateTransaction } = useWorldStore(); const observerWrites = useStore(store, (state) => state.writes); + const { toasts } = useSonner(); const handleTransaction = useCallback( async (hash: Hex, timestamp: bigint) => { @@ -44,14 +52,17 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { } if (pathname !== getLinkUrl("observe")) { - // TODO: submit toast with new tx - toast(`${functionName}(${args?.join(", ")})`, { - description: `Tx hash: ${hash}`, - action: { - label: "Open", - onClick: () => console.log("Open"), - }, - }); + const existingToast = toasts.find((toast) => toast.id === hash); + if (!existingToast) { + toast(`${functionName}(${args?.map(formatHexInput).join(", ")})`, { + id: hash, + description: `Hash: ${formatHexInput(hash)}`, + action: { + label: "Open", + onClick: () => console.log("Open"), + }, + }); + } } const write = Object.values(observerWrites).find((write) => write.hash === hash); @@ -108,7 +119,7 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { error: transactionError as BaseError, }); }, - [abi, wagmiConfig, worldAddress, pathname, getLinkUrl, observerWrites, setTransaction, updateTransaction], + [abi, wagmiConfig, worldAddress, pathname, getLinkUrl, observerWrites, setTransaction, updateTransaction, toasts], ); useEffect(() => { diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts index 60cc3c543f..1aa4bb71cb 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts @@ -10,12 +10,10 @@ export type AppStoreData = { export const createWorldStore = () => { return createStore()((set) => ({ transactions: [], - setTransaction: (transaction) => set((state) => ({ transactions: [...state.transactions, transaction], })), - updateTransaction: (hash: string, updatedTransaction: Partial) => set((state) => ({ transactions: state.transactions.map((tx) => (tx.hash === hash ? { ...tx, ...updatedTransaction } : tx)), From 45767410c0fc81039414ebe51cada7ef065cf07b Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 16:18:00 +0300 Subject: [PATCH 03/15] undo toasts --- .../explore/EditableTableCell.tsx | 14 ++++++--- .../observe/TransactionsWatcher.tsx | 30 ++----------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx index a67f9c1ff1..b4877a7316 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx @@ -58,11 +58,15 @@ export function EditableTableCell({ name, table, keyTuple, value: defaultValue } return { txHash, receipt }; }, - onSuccess: ({ txHash }, newValue) => { + onMutate: () => { + const toastId = toast.loading("Transaction submitted"); + return { toastId }; + }, + onSuccess: ({ txHash }, newValue, { toastId }) => { setValue(newValue); toast.success(`Transaction successful with hash: ${txHash}`, { - id: txHash, + id: toastId, }); queryClient.invalidateQueries({ queryKey: [ @@ -74,9 +78,11 @@ export function EditableTableCell({ name, table, keyTuple, value: defaultValue } ], }); }, - onError: (error) => { + onError: (error, _, context) => { console.error("Error:", error); - toast.error(error.message || "Something went wrong. Please try again."); + toast.error(error.message || "Something went wrong. Please try again.", { + id: context?.toastId, + }); setValue(defaultValue); }, }); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index 3a3f744902..fcd91be1fb 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -1,5 +1,4 @@ -import { useParams, usePathname } from "next/navigation"; -import { toast, useSonner } from "sonner"; +import { useParams } from "next/navigation"; import { BaseError, Hex, TransactionReceipt, decodeFunctionData, parseEventLogs } from "viem"; import { useConfig, useWatchBlocks } from "wagmi"; import { getTransaction, simulateContract, waitForTransactionReceipt } from "wagmi/actions"; @@ -8,20 +7,10 @@ import { ReactNode } from "react"; import { useCallback, useEffect } from "react"; import { store } from "../../../../../../observer/store"; import { useChain } from "../../../../hooks/useChain"; -import { useWorldUrl } from "../../../../hooks/useWorldUrl"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; import { useWorldStore } from "../store/useWorldStore"; -const formatHexInput = (input: unknown): string => { - if (typeof input === "string" && input.startsWith("0x")) { - return input.length > 10 ? `${input.slice(0, 6)}...${input.slice(-4)}` : input; - } - return String(input); -}; - export function TransactionsWatcher({ children }: { children: ReactNode }) { - const getLinkUrl = useWorldUrl(); - const pathname = usePathname(); const { id: chainId } = useChain(); const { worldAddress } = useParams(); const wagmiConfig = useConfig(); @@ -29,7 +18,6 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { const abi = worldAbiData?.abi; const { transactions, setTransaction, updateTransaction } = useWorldStore(); const observerWrites = useStore(store, (state) => state.writes); - const { toasts } = useSonner(); const handleTransaction = useCallback( async (hash: Hex, timestamp: bigint) => { @@ -51,20 +39,6 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { functionName = transaction.input.length > 10 ? transaction.input.slice(0, 10) : "unknown"; } - if (pathname !== getLinkUrl("observe")) { - const existingToast = toasts.find((toast) => toast.id === hash); - if (!existingToast) { - toast(`${functionName}(${args?.map(formatHexInput).join(", ")})`, { - id: hash, - description: `Hash: ${formatHexInput(hash)}`, - action: { - label: "Open", - onClick: () => console.log("Open"), - }, - }); - } - } - const write = Object.values(observerWrites).find((write) => write.hash === hash); setTransaction({ hash, @@ -119,7 +93,7 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { error: transactionError as BaseError, }); }, - [abi, wagmiConfig, worldAddress, pathname, getLinkUrl, observerWrites, setTransaction, updateTransaction, toasts], + [abi, wagmiConfig, worldAddress, observerWrites, setTransaction, updateTransaction], ); useEffect(() => { From c96f4617a0549f39e3d0c7384606c51c37de152b Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 16:19:41 +0300 Subject: [PATCH 04/15] remove empty line --- .../worlds/[worldAddress]/explore/EditableTableCell.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx index b4877a7316..921add083e 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/EditableTableCell.tsx @@ -64,7 +64,6 @@ export function EditableTableCell({ name, table, keyTuple, value: defaultValue } }, onSuccess: ({ txHash }, newValue, { toastId }) => { setValue(newValue); - toast.success(`Transaction successful with hash: ${txHash}`, { id: toastId, }); From dfe2fce0ab7af02ca4f453a57c9774a4e2d50090 Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Mon, 14 Oct 2024 16:21:52 +0300 Subject: [PATCH 05/15] Create tall-penguins-promise.md --- .changeset/tall-penguins-promise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-penguins-promise.md diff --git a/.changeset/tall-penguins-promise.md b/.changeset/tall-penguins-promise.md new file mode 100644 index 0000000000..59adfb99e5 --- /dev/null +++ b/.changeset/tall-penguins-promise.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Transactions are now monitored across all tabs while the World Explorer is open. From 6f5cca082364052a1112990fee569933fa6029f7 Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 17:22:46 +0300 Subject: [PATCH 06/15] simplify store --- .../worlds/[worldAddress]/Providers.tsx | 5 +--- .../worlds/[worldAddress]/layout.tsx | 7 +++-- .../observe/TransactionsWatcher.tsx | 8 +++--- .../observe/useTransactionsWatcher.ts | 8 +++--- .../worlds/[worldAddress]/store.ts | 20 ++++++++++++++ .../store/WorldStoreProvider.tsx | 26 ------------------- .../[worldAddress]/store/createWorldStore.ts | 22 ---------------- .../worlds/[worldAddress]/store/index.ts | 3 --- .../[worldAddress]/store/useWorldStore.ts | 13 ---------- 9 files changed, 34 insertions(+), 78 deletions(-) create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts delete mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx delete mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts delete mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts delete mode 100644 packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx index 07694ae414..614b38186c 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx @@ -8,7 +8,6 @@ import "@rainbow-me/rainbowkit/styles.css"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { getDefaultAnvilConnectors } from "../../../../../connectors/anvil"; import { useChain } from "../../../hooks/useChain"; -import { WorldStoreProvider } from "./store"; const queryClient = new QueryClient(); @@ -37,9 +36,7 @@ export function Providers({ children }: { children: ReactNode }) { return ( - - {children} - + {children} ); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx index 5b4c354560..0f7ae73e50 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx @@ -2,12 +2,15 @@ import { Navigation } from "../../../../../components/Navigation"; import { Providers } from "./Providers"; +import { TransactionsWatcher } from "./observe/TransactionsWatcher"; export default function WorldLayout({ children }: { children: React.ReactNode }) { return ( - - {children} + + + {children} + ); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index fcd91be1fb..25cbca4077 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -5,10 +5,10 @@ import { getTransaction, simulateContract, waitForTransactionReceipt } from "wag import { useStore } from "zustand"; import { ReactNode } from "react"; import { useCallback, useEffect } from "react"; -import { store } from "../../../../../../observer/store"; +import { store as observerStore } from "../../../../../../observer/store"; import { useChain } from "../../../../hooks/useChain"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; -import { useWorldStore } from "../store/useWorldStore"; +import { store as worldStore } from "../store"; export function TransactionsWatcher({ children }: { children: ReactNode }) { const { id: chainId } = useChain(); @@ -16,8 +16,8 @@ export function TransactionsWatcher({ children }: { children: ReactNode }) { const wagmiConfig = useConfig(); const { data: worldAbiData } = useWorldAbiQuery(); const abi = worldAbiData?.abi; - const { transactions, setTransaction, updateTransaction } = useWorldStore(); - const observerWrites = useStore(store, (state) => state.writes); + const { transactions, setTransaction, updateTransaction } = useStore(worldStore); + const observerWrites = useStore(observerStore, (state) => state.writes); const handleTransaction = useCallback( async (hash: Hex, timestamp: bigint) => { diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts index 749919907d..1e45b5ca9d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts @@ -13,8 +13,8 @@ import { import { useStore } from "zustand"; import { useMemo } from "react"; import { Message } from "../../../../../../observer/messages"; -import { type Write, store } from "../../../../../../observer/store"; -import { useWorldStore } from "../store/useWorldStore"; +import { type Write, store as observerStore } from "../../../../../../observer/store"; +import { store as worldStore } from "../store"; export type WatchedTransaction = { writeId: string; @@ -33,8 +33,8 @@ export type WatchedTransaction = { export function useTransactionsWatcher() { const { worldAddress } = useParams(); - const { transactions } = useWorldStore(); - const observerWrites = useStore(store, (state) => state.writes); + const { transactions } = useStore(worldStore); + const observerWrites = useStore(observerStore, (state) => state.writes); const mergedTransactions = useMemo((): WatchedTransaction[] => { const mergedMap = new Map(); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts new file mode 100644 index 0000000000..5a7019cafc --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts @@ -0,0 +1,20 @@ +import { createStore } from "zustand"; +import { WatchedTransaction } from "./observe/useTransactionsWatcher"; + +export type State = { + transactions: WatchedTransaction[]; + setTransaction: (transaction: WatchedTransaction) => void; + updateTransaction: (hash: string, updatedTransaction: Partial) => void; +}; + +export const store = createStore()((set) => ({ + transactions: [], + setTransaction: (transaction) => + set((state) => ({ + transactions: [...state.transactions, transaction], + })), + updateTransaction: (hash: string, updatedTransaction: Partial) => + set((state) => ({ + transactions: state.transactions.map((tx) => (tx.hash === hash ? { ...tx, ...updatedTransaction } : tx)), + })), +})); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx deleted file mode 100644 index aed7700633..0000000000 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/WorldStoreProvider.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { type ReactNode, createContext, useRef } from "react"; -import { TransactionsWatcher } from "../observe/TransactionsWatcher"; -import { createWorldStore } from "./createWorldStore"; - -export type WorldStore = ReturnType; - -export const WorldStoreContext = createContext(undefined); - -type Props = { - children: ReactNode; -}; - -export const WorldStoreProvider = ({ children }: Props) => { - const storeRef = useRef(); - if (!storeRef.current) { - storeRef.current = createWorldStore(); - } - - return ( - - {children} - - ); -}; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts deleted file mode 100644 index 1aa4bb71cb..0000000000 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/createWorldStore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createStore } from "zustand"; -import { WatchedTransaction } from "../observe/useTransactionsWatcher"; - -export type AppStoreData = { - transactions: WatchedTransaction[]; - setTransaction: (transaction: WatchedTransaction) => void; - updateTransaction: (hash: string, updatedTransaction: Partial) => void; -}; - -export const createWorldStore = () => { - return createStore()((set) => ({ - transactions: [], - setTransaction: (transaction) => - set((state) => ({ - transactions: [...state.transactions, transaction], - })), - updateTransaction: (hash: string, updatedTransaction: Partial) => - set((state) => ({ - transactions: state.transactions.map((tx) => (tx.hash === hash ? { ...tx, ...updatedTransaction } : tx)), - })), - })); -}; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts deleted file mode 100644 index cbbcc1d675..0000000000 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./createWorldStore"; -export * from "./useWorldStore"; -export * from "./WorldStoreProvider"; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts deleted file mode 100644 index ed28605170..0000000000 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store/useWorldStore.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useStore } from "zustand"; -import { useContext } from "react"; -import { WorldStoreContext } from "./WorldStoreProvider"; - -export const useWorldStore = () => { - const worldStoreContext = useContext(WorldStoreContext); - - if (!worldStoreContext) { - throw new Error(`useWorldStore must be used within WorldStoreProvider`); - } - - return useStore(worldStoreContext); -}; From 6ef212a2ab401a01faf37f8fff5fbc41bd5e14cc Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 14 Oct 2024 17:24:06 +0300 Subject: [PATCH 07/15] adjust ReactNode prop --- .../worlds/[worldAddress]/observe/TransactionsWatcher.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index 25cbca4077..076d17f6fd 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -3,14 +3,13 @@ import { BaseError, Hex, TransactionReceipt, decodeFunctionData, parseEventLogs import { useConfig, useWatchBlocks } from "wagmi"; import { getTransaction, simulateContract, waitForTransactionReceipt } from "wagmi/actions"; import { useStore } from "zustand"; -import { ReactNode } from "react"; import { useCallback, useEffect } from "react"; import { store as observerStore } from "../../../../../../observer/store"; import { useChain } from "../../../../hooks/useChain"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; import { store as worldStore } from "../store"; -export function TransactionsWatcher({ children }: { children: ReactNode }) { +export function TransactionsWatcher({ children }: { children: React.ReactNode }) { const { id: chainId } = useChain(); const { worldAddress } = useParams(); const wagmiConfig = useConfig(); From 99f49455782c15c0de24a863459241b16f533725 Mon Sep 17 00:00:00 2001 From: karooolis Date: Tue, 15 Oct 2024 11:38:29 +0300 Subject: [PATCH 08/15] init observer store, process events --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 5 +++++ .../[chainName]/worlds/[worldAddress]/layout.tsx | 7 +++---- .../worlds/[worldAddress]/observe/TransactionsWatcher.tsx | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index a17b413515..ab8f46637a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -4,6 +4,7 @@ import Image from "next/image"; import { useParams, useRouter } from "next/navigation"; import { Address, isAddress } from "viem"; import * as z from "zod"; +import { useStore } from "zustand"; import { Command as CommandPrimitive } from "cmdk"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -12,6 +13,7 @@ import { Button } from "../../../../components/ui/Button"; import { Command, CommandGroup, CommandItem, CommandList } from "../../../../components/ui/Command"; import { Form, FormControl, FormField, FormItem, FormMessage } from "../../../../components/ui/Form"; import { Input } from "../../../../components/ui/Input"; +import { store } from "../../../../observer/store"; import mudLogo from "../../icon.svg"; import { getWorldUrl } from "../../utils/getWorldUrl"; @@ -25,6 +27,9 @@ const formSchema = z.object({ }); export function WorldsForm({ worlds }: { worlds: Address[] }) { + // Initialize the observer store to start fetching transactions + useStore(store); + const router = useRouter(); const { chainName } = useParams(); const [open, setOpen] = useState(false); diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx index 0f7ae73e50..2c8b5a4811 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx @@ -7,10 +7,9 @@ import { TransactionsWatcher } from "./observe/TransactionsWatcher"; export default function WorldLayout({ children }: { children: React.ReactNode }) { return ( - - - {children} - + + + {children} ); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx index 076d17f6fd..8cb52a2657 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionsWatcher.tsx @@ -9,7 +9,7 @@ import { useChain } from "../../../../hooks/useChain"; import { useWorldAbiQuery } from "../../../../queries/useWorldAbiQuery"; import { store as worldStore } from "../store"; -export function TransactionsWatcher({ children }: { children: React.ReactNode }) { +export function TransactionsWatcher() { const { id: chainId } = useChain(); const { worldAddress } = useParams(); const wagmiConfig = useConfig(); @@ -98,7 +98,7 @@ export function TransactionsWatcher({ children }: { children: React.ReactNode }) useEffect(() => { for (const write of Object.values(observerWrites)) { const hash = write.hash; - if (write.type === "waitForTransactionReceipt" && hash && write.address === worldAddress) { + if (hash && write.address === worldAddress) { const transaction = transactions.find((transaction) => transaction.hash === hash); if (!transaction) { handleTransaction(hash, BigInt(write.time) / 1000n); @@ -118,5 +118,5 @@ export function TransactionsWatcher({ children }: { children: React.ReactNode }) pollingInterval: 500, }); - return children; + return null; } From eb1c9549fba6db5c77a14fe3401c110d8f9edde4 Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 16 Oct 2024 12:45:57 +0300 Subject: [PATCH 09/15] update to useObservedTransactions --- .../[worldAddress]/observe/TransactionTableRow.tsx | 6 +++--- .../[worldAddress]/observe/TransactionsTable.tsx | 6 +++--- ...ansactionsWatcher.ts => useObservedTransactions.ts} | 8 ++++---- .../[chainName]/worlds/[worldAddress]/store.ts | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) rename packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/{useTransactionsWatcher.ts => useObservedTransactions.ts} (91%) 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 cc2a96a67c..d828a5436f 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 @@ -8,7 +8,7 @@ import { cn } from "../../../../../../utils"; import { Confirmations } from "./Confirmations"; import { TimingRowExpanded } from "./TimingRowExpanded"; import { columns } from "./TransactionsTable"; -import { WatchedTransaction } from "./useTransactionsWatcher"; +import { ObservedTransaction } from "./useObservedTransactions"; function TransactionTableRowDataCell({ label, @@ -16,7 +16,7 @@ function TransactionTableRowDataCell({ children, }: { label: string; - status: WatchedTransaction["status"]; + status: ObservedTransaction["status"]; children: React.ReactNode; }) { return ( @@ -30,7 +30,7 @@ function TransactionTableRowDataCell({ ); } -export function TransactionTableRow({ row }: { row: Row }) { +export function TransactionTableRow({ row }: { row: Row }) { const data = row?.original; const status = data.status; const logs = data?.logs; 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 5e903197a6..06fcbda1a8 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 @@ -12,9 +12,9 @@ import { BlockExplorerLink } from "./BlockExplorerLink"; import { TimeAgo } from "./TimeAgo"; import { TimingRowHeader } from "./TimingRowHeader"; import { TransactionTableRow } from "./TransactionTableRow"; -import { WatchedTransaction, useTransactionsWatcher } from "./useTransactionsWatcher"; +import { ObservedTransaction, useObservedTransactions } from "./useObservedTransactions"; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); export const columns = [ columnHelper.accessor("receipt.blockNumber", { header: "Block", @@ -94,7 +94,7 @@ export const columns = [ ]; export function TransactionsTable() { - const transactions = useTransactionsWatcher(); + const transactions = useObservedTransactions(); const [expanded, setExpanded] = useState({}); const table = useReactTable({ diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts similarity index 91% rename from packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts rename to packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts index 1e45b5ca9d..3203cd2b94 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useTransactionsWatcher.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts @@ -16,7 +16,7 @@ import { Message } from "../../../../../../observer/messages"; import { type Write, store as observerStore } from "../../../../../../observer/store"; import { store as worldStore } from "../store"; -export type WatchedTransaction = { +export type ObservedTransaction = { writeId: string; hash?: Hex; from?: Address; @@ -31,13 +31,13 @@ export type WatchedTransaction = { error?: BaseError; }; -export function useTransactionsWatcher() { +export function useObservedTransactions() { const { worldAddress } = useParams(); const { transactions } = useStore(worldStore); const observerWrites = useStore(observerStore, (state) => state.writes); - const mergedTransactions = useMemo((): WatchedTransaction[] => { - const mergedMap = new Map(); + const mergedTransactions = useMemo((): ObservedTransaction[] => { + const mergedMap = new Map(); for (const write of Object.values(observerWrites)) { if (write.address !== worldAddress) continue; diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts index 5a7019cafc..007159fcf1 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/store.ts @@ -1,10 +1,10 @@ import { createStore } from "zustand"; -import { WatchedTransaction } from "./observe/useTransactionsWatcher"; +import { ObservedTransaction } from "./observe/useObservedTransactions"; export type State = { - transactions: WatchedTransaction[]; - setTransaction: (transaction: WatchedTransaction) => void; - updateTransaction: (hash: string, updatedTransaction: Partial) => void; + transactions: ObservedTransaction[]; + setTransaction: (transaction: ObservedTransaction) => void; + updateTransaction: (hash: string, updatedTransaction: Partial) => void; }; export const store = createStore()((set) => ({ @@ -13,7 +13,7 @@ export const store = createStore()((set) => ({ set((state) => ({ transactions: [...state.transactions, transaction], })), - updateTransaction: (hash: string, updatedTransaction: Partial) => + updateTransaction: (hash: string, updatedTransaction: Partial) => set((state) => ({ transactions: state.transactions.map((tx) => (tx.hash === hash ? { ...tx, ...updatedTransaction } : tx)), })), From d44343d16c271315cd2bbbbb35262475462e14fc Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 16 Oct 2024 12:46:30 +0300 Subject: [PATCH 10/15] narrow down to state transactions --- .../worlds/[worldAddress]/observe/useObservedTransactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts index 3203cd2b94..ff49086f28 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/useObservedTransactions.ts @@ -33,7 +33,7 @@ export type ObservedTransaction = { export function useObservedTransactions() { const { worldAddress } = useParams(); - const { transactions } = useStore(worldStore); + const transactions = useStore(worldStore, (state) => state.transactions); const observerWrites = useStore(observerStore, (state) => state.writes); const mergedTransactions = useMemo((): ObservedTransaction[] => { From da7566f0756a785f06d97f97541be62b9e6088c5 Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Wed, 16 Oct 2024 13:35:14 +0300 Subject: [PATCH 11/15] Update packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx Co-authored-by: Kevin Ingersoll --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index ab8f46637a..dc5d6958a6 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -28,7 +28,7 @@ const formSchema = z.object({ export function WorldsForm({ worlds }: { worlds: Address[] }) { // Initialize the observer store to start fetching transactions - useStore(store); + store; const router = useRouter(); const { chainName } = useParams(); From 6a39d1d35e64aea43c0a8842e862fa2c5aac80eb Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 16 Oct 2024 13:39:07 +0300 Subject: [PATCH 12/15] remove useStore dep --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index dc5d6958a6..2751c41716 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -4,7 +4,6 @@ import Image from "next/image"; import { useParams, useRouter } from "next/navigation"; import { Address, isAddress } from "viem"; import * as z from "zod"; -import { useStore } from "zustand"; import { Command as CommandPrimitive } from "cmdk"; import { useState } from "react"; import { useForm } from "react-hook-form"; From 1ce297eab6947fa72ed083b3818332286eade229 Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Wed, 16 Oct 2024 13:45:13 +0300 Subject: [PATCH 13/15] Update packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx Co-authored-by: Kevin Ingersoll --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index 2751c41716..774b405c67 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -26,7 +26,7 @@ const formSchema = z.object({ }); export function WorldsForm({ worlds }: { worlds: Address[] }) { - // Initialize the observer store to start fetching transactions + // Reference to imported store observer so the listener is established when this component is used. store; const router = useRouter(); From 800f5d5e2a80f0560bda467390e3496c068f4129 Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 16 Oct 2024 13:54:00 +0300 Subject: [PATCH 14/15] move store ref outside component --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index 774b405c67..19e6ddde13 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -16,6 +16,9 @@ import { store } from "../../../../observer/store"; import mudLogo from "../../icon.svg"; import { getWorldUrl } from "../../utils/getWorldUrl"; +// Reference to imported store observer so the listener is established when this component is used. +store; + const formSchema = z.object({ worldAddress: z .string() @@ -26,9 +29,6 @@ const formSchema = z.object({ }); export function WorldsForm({ worlds }: { worlds: Address[] }) { - // Reference to imported store observer so the listener is established when this component is used. - store; - const router = useRouter(); const { chainName } = useParams(); const [open, setOpen] = useState(false); From 36b42063b57de764e2c2dbaf36e508751c5e54aa Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 16 Oct 2024 13:59:17 +0300 Subject: [PATCH 15/15] move store into layout --- .../src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx | 4 ---- packages/explorer/src/app/(explorer)/layout.tsx | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index 19e6ddde13..a17b413515 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -12,13 +12,9 @@ import { Button } from "../../../../components/ui/Button"; import { Command, CommandGroup, CommandItem, CommandList } from "../../../../components/ui/Command"; import { Form, FormControl, FormField, FormItem, FormMessage } from "../../../../components/ui/Form"; import { Input } from "../../../../components/ui/Input"; -import { store } from "../../../../observer/store"; import mudLogo from "../../icon.svg"; import { getWorldUrl } from "../../utils/getWorldUrl"; -// Reference to imported store observer so the listener is established when this component is used. -store; - const formSchema = z.object({ worldAddress: z .string() diff --git a/packages/explorer/src/app/(explorer)/layout.tsx b/packages/explorer/src/app/(explorer)/layout.tsx index 149a752f55..8c630fc884 100644 --- a/packages/explorer/src/app/(explorer)/layout.tsx +++ b/packages/explorer/src/app/(explorer)/layout.tsx @@ -3,8 +3,12 @@ import { Inter, JetBrains_Mono } from "next/font/google"; import { Toaster } from "sonner"; import { Theme } from "@radix-ui/themes"; import "@radix-ui/themes/styles.css"; +import { store } from "../../observer/store"; import "./globals.css"; +// Reference to imported store observer so the listener is established when this component is used. +store; + const inter = Inter({ subsets: ["latin"], display: "swap",