Skip to content

Commit

Permalink
use websockets client in useFetchBlocks hooks (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
portdeveloper authored Sep 21, 2023
1 parent e3b4bd2 commit ebfb874
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 100 deletions.
99 changes: 41 additions & 58 deletions packages/nextjs/components/blockexplorer/TransactionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Address } from "~~/components/scaffold-eth";
import { TransactionWithFunction, getTargetNetwork } from "~~/utils/scaffold-eth";
import { TransactionsTableProps } from "~~/utils/scaffold-eth/";

export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: TransactionsTableProps) => {
export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsTableProps) => {
const targetNetwork = getTargetNetwork();

return (
Expand All @@ -22,64 +22,47 @@ export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: Tr
<th className="bg-primary text-end">Value ({targetNetwork.nativeCurrency.symbol})</th>
</tr>
</thead>
{isLoading ? (
<tbody>
{[...Array(20)].map((_, rowIndex) => (
<tr
key={rowIndex}
className="bg-base-200 hover:bg-base-300 transition-colors duration-200 h-12 text-sm"
>
{[...Array(7)].map((_, colIndex) => (
<td className="w-1/12 md:py-4" key={colIndex}>
<div className="h-2 bg-gray-200 rounded-full animate-pulse"></div>
</td>
))}
</tr>
))}
</tbody>
) : (
<tbody>
{blocks.map(block =>
(block.transactions as TransactionWithFunction[]).map(tx => {
const receipt = transactionReceipts[tx.hash];
const timeMined = new Date(Number(block.timestamp) * 1000).toLocaleString();
const functionCalled = tx.input.substring(0, 10);
<tbody>
{blocks.map(block =>
(block.transactions as TransactionWithFunction[]).map(tx => {
const receipt = transactionReceipts[tx.hash];
const timeMined = new Date(Number(block.timestamp) * 1000).toLocaleString();
const functionCalled = tx.input.substring(0, 10);

return (
<tr key={tx.hash} className="hover text-sm">
<td className="w-1/12 md:py-4">
<TransactionHash hash={tx.hash} />
</td>
<td className="w-2/12 md:py-4">
{tx.functionName === "0x" ? "" : <span className="mr-1">{tx.functionName}</span>}
{functionCalled !== "0x" && (
<span className="badge badge-primary font-bold text-xs">{functionCalled}</span>
)}
</td>
<td className="w-1/12 md:py-4">{block.number?.toString()}</td>
<td className="w-2/1 md:py-4">{timeMined}</td>
<td className="w-2/12 md:py-4">
<Address address={tx.from} size="sm" />
</td>
<td className="w-2/12 md:py-4">
{!receipt?.contractAddress ? (
tx.to && <Address address={tx.to} size="sm" />
) : (
<div className="relative">
<Address address={receipt.contractAddress} size="sm" />
<small className="absolute top-4 left-4">(Contract Creation)</small>
</div>
)}
</td>
<td className="text-right md:py-4">
{formatEther(tx.value)} {targetNetwork.nativeCurrency.symbol}
</td>
</tr>
);
}),
)}
</tbody>
)}
return (
<tr key={tx.hash} className="hover text-sm">
<td className="w-1/12 md:py-4">
<TransactionHash hash={tx.hash} />
</td>
<td className="w-2/12 md:py-4">
{tx.functionName === "0x" ? "" : <span className="mr-1">{tx.functionName}</span>}
{functionCalled !== "0x" && (
<span className="badge badge-primary font-bold text-xs">{functionCalled}</span>
)}
</td>
<td className="w-1/12 md:py-4">{block.number?.toString()}</td>
<td className="w-2/1 md:py-4">{timeMined}</td>
<td className="w-2/12 md:py-4">
<Address address={tx.from} size="sm" />
</td>
<td className="w-2/12 md:py-4">
{!receipt?.contractAddress ? (
tx.to && <Address address={tx.to} size="sm" />
) : (
<div className="relative">
<Address address={receipt.contractAddress} size="sm" />
<small className="absolute top-4 left-4">(Contract Creation)</small>
</div>
)}
</td>
<td className="text-right md:py-4">
{formatEther(tx.value)} {targetNetwork.nativeCurrency.symbol}
</td>
</tr>
);
}),
)}
</tbody>
</table>
</div>
</div>
Expand Down
86 changes: 50 additions & 36 deletions packages/nextjs/hooks/scaffold-eth/useFetchBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import { useCallback, useEffect, useState } from "react";
import { Block, Transaction, TransactionReceipt } from "viem";
import { usePublicClient } from "wagmi";
import {
Block,
Hash,
Transaction,
TransactionReceipt,
createTestClient,
publicActions,
walletActions,
webSocket,
} from "viem";
import { hardhat } from "wagmi/chains";
import { decodeTransactionData } from "~~/utils/scaffold-eth";

const BLOCKS_PER_PAGE = 20;

export const useFetchBlocks = () => {
const client = usePublicClient({ chainId: hardhat.id });
export const testClient = createTestClient({
chain: hardhat,
mode: "hardhat",
transport: webSocket("ws://127.0.0.1:8545"),
})
.extend(publicActions)
.extend(walletActions);

export const useFetchBlocks = () => {
const [blocks, setBlocks] = useState<Block[]>([]);
const [transactionReceipts, setTransactionReceipts] = useState<{
[key: string]: TransactionReceipt;
}>({});
const [currentPage, setCurrentPage] = useState(0);
const [totalBlocks, setTotalBlocks] = useState(0n);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);

const fetchBlocks = useCallback(async () => {
setIsLoading(true);
setError(null);

try {
const blockNumber = await client.getBlockNumber();
const blockNumber = await testClient.getBlockNumber();
setTotalBlocks(blockNumber);

const startingBlock = blockNumber - BigInt(currentPage * BLOCKS_PER_PAGE);
Expand All @@ -34,7 +46,7 @@ export const useFetchBlocks = () => {

const blocksWithTransactions = blockNumbersToFetch.map(async blockNumber => {
try {
return client.getBlock({ blockNumber, includeTransactions: true });
return testClient.getBlock({ blockNumber, includeTransactions: true });
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
throw err;
Expand All @@ -50,7 +62,7 @@ export const useFetchBlocks = () => {
fetchedBlocks.flatMap(block =>
block.transactions.map(async tx => {
try {
const receipt = await client.getTransactionReceipt({ hash: (tx as Transaction).hash });
const receipt = await testClient.getTransactionReceipt({ hash: (tx as Transaction).hash });
return { [(tx as Transaction).hash]: receipt };
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
Expand All @@ -65,55 +77,57 @@ export const useFetchBlocks = () => {
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
}
setIsLoading(false);
}, [client, currentPage]);
}, [currentPage]);

useEffect(() => {
fetchBlocks();
}, [fetchBlocks]);

useEffect(() => {
const handleNewBlock = async (newBlock: Block) => {
const handleNewBlock = async (newBlock: any) => {
try {
if (!blocks.some(block => block.number === newBlock.number)) {
if (currentPage === 0) {
setBlocks(prevBlocks => [newBlock, ...prevBlocks.slice(0, BLOCKS_PER_PAGE - 1)]);

newBlock.transactions.forEach(tx => decodeTransactionData(tx as Transaction));

const receipts = await Promise.all(
newBlock.transactions.map(async tx => {
try {
const receipt = await client.getTransactionReceipt({ hash: (tx as Transaction).hash });
return { [(tx as Transaction).hash]: receipt };
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
throw err;
}
}),
if (currentPage === 0) {
if (newBlock.transactions.length > 0) {
const transactionsDetails = await Promise.all(
newBlock.transactions.map((txHash: string) => testClient.getTransaction({ hash: txHash as Hash })),
);

setTransactionReceipts(prevReceipts => ({ ...prevReceipts, ...Object.assign({}, ...receipts) }));
}
if (newBlock.number) {
setTotalBlocks(newBlock.number);
newBlock.transactions = transactionsDetails;
}

newBlock.transactions.forEach((tx: Transaction) => decodeTransactionData(tx as Transaction));

const receipts = await Promise.all(
newBlock.transactions.map(async (tx: Transaction) => {
try {
const receipt = await testClient.getTransactionReceipt({ hash: (tx as Transaction).hash });
return { [(tx as Transaction).hash]: receipt };
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred fetching receipt."));
throw err;
}
}),
);

setBlocks(prevBlocks => [newBlock, ...prevBlocks.slice(0, BLOCKS_PER_PAGE - 1)]);
setTransactionReceipts(prevReceipts => ({ ...prevReceipts, ...Object.assign({}, ...receipts) }));
}
if (newBlock.number) {
setTotalBlocks(newBlock.number);
}
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
}
};

return client.watchBlocks({ onBlock: handleNewBlock, includeTransactions: true });
}, [blocks, client, currentPage]);
return testClient.watchBlocks({ onBlock: handleNewBlock, includeTransactions: true });
}, [currentPage]);

return {
blocks,
transactionReceipts,
currentPage,
totalBlocks,
setCurrentPage,
isLoading,
error,
};
};
4 changes: 2 additions & 2 deletions packages/nextjs/pages/blockexplorer/address/[address].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const publicClient = createPublicClient({

const AddressPage = ({ address, contractData }: PageProps) => {
const router = useRouter();
const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage, isLoading } = useFetchBlocks();
const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage } = useFetchBlocks();
const [activeTab, setActiveTab] = useState("transactions");
const [isContract, setIsContract] = useState(false);

Expand Down Expand Up @@ -108,7 +108,7 @@ const AddressPage = ({ address, contractData }: PageProps) => {
)}
{activeTab === "transactions" && (
<div className="pt-4">
<TransactionsTable blocks={filteredBlocks} transactionReceipts={transactionReceipts} isLoading={isLoading} />
<TransactionsTable blocks={filteredBlocks} transactionReceipts={transactionReceipts} />
<PaginationButton
currentPage={currentPage}
totalItems={Number(totalBlocks)}
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/pages/blockexplorer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useFetchBlocks } from "~~/hooks/scaffold-eth";
import { getTargetNetwork, notification } from "~~/utils/scaffold-eth";

const Blockexplorer: NextPage = () => {
const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage, isLoading, error } = useFetchBlocks();
const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage, error } = useFetchBlocks();

useEffect(() => {
if (getTargetNetwork().id === hardhat.id && error) {
Expand Down Expand Up @@ -51,7 +51,7 @@ const Blockexplorer: NextPage = () => {
return (
<div className="container mx-auto my-10">
<SearchBar />
<TransactionsTable blocks={blocks} transactionReceipts={transactionReceipts} isLoading={isLoading} />
<TransactionsTable blocks={blocks} transactionReceipts={transactionReceipts} />
<PaginationButton currentPage={currentPage} totalItems={Number(totalBlocks)} setCurrentPage={setCurrentPage} />
</div>
);
Expand Down
1 change: 0 additions & 1 deletion packages/nextjs/utils/scaffold-eth/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@ interface TransactionReceipts {
export interface TransactionsTableProps {
blocks: Block[];
transactionReceipts: TransactionReceipts;
isLoading: boolean;
}
2 changes: 1 addition & 1 deletion packages/nextjs/utils/scaffold-eth/decodeTxData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const interfaces = chainMetaData
: {};

export const decodeTransactionData = (tx: TransactionWithFunction) => {
if (tx.input.length >= 10) {
if (tx.input.length >= 10 && !tx.input.startsWith("0x60e06040")) {
for (const [, contractAbi] of Object.entries(interfaces)) {
try {
const { functionName, args } = decodeFunctionData({
Expand Down

0 comments on commit ebfb874

Please sign in to comment.