create-eth
Version:
Create a Scaffold-ETH-2 app
134 lines (116 loc) • 4.41 kB
text/typescript
import { useCallback, useEffect, useState } from "react";
import {
Block,
Hash,
Transaction,
TransactionReceipt,
createTestClient,
publicActions,
walletActions,
webSocket,
} from "viem";
import { hardhat } from "viem/chains";
import { decodeTransactionData } from "~~/utils/scaffold-eth";
const BLOCKS_PER_PAGE = 20;
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 [error, setError] = useState<Error | null>(null);
const fetchBlocks = useCallback(async () => {
setError(null);
try {
const blockNumber = await testClient.getBlockNumber();
setTotalBlocks(blockNumber);
const startingBlock = blockNumber - BigInt(currentPage * BLOCKS_PER_PAGE);
const blockNumbersToFetch = Array.from(
{ length: Number(BLOCKS_PER_PAGE < startingBlock + 1n ? BLOCKS_PER_PAGE : startingBlock + 1n) },
(_, i) => startingBlock - BigInt(i),
);
const blocksWithTransactions = blockNumbersToFetch.map(async blockNumber => {
try {
return testClient.getBlock({ blockNumber, includeTransactions: true });
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
throw err;
}
});
const fetchedBlocks = await Promise.all(blocksWithTransactions);
fetchedBlocks.forEach(block => {
block.transactions.forEach(tx => decodeTransactionData(tx as Transaction));
});
const txReceipts = await Promise.all(
fetchedBlocks.flatMap(block =>
block.transactions.map(async tx => {
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."));
throw err;
}
}),
),
);
setBlocks(fetchedBlocks);
setTransactionReceipts(prevReceipts => ({ ...prevReceipts, ...Object.assign({}, ...txReceipts) }));
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred."));
}
}, [currentPage]);
useEffect(() => {
fetchBlocks();
}, [fetchBlocks]);
useEffect(() => {
const handleNewBlock = async (newBlock: any) => {
try {
if (currentPage === 0) {
if (newBlock.transactions.length > 0) {
const transactionsDetails = await Promise.all(
newBlock.transactions.map((txHash: string) => testClient.getTransaction({ hash: txHash as Hash })),
);
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 testClient.watchBlocks({ onBlock: handleNewBlock, includeTransactions: true });
}, [currentPage]);
return {
blocks,
transactionReceipts,
currentPage,
totalBlocks,
setCurrentPage,
error,
};
};