UNPKG

xchange-create

Version:
204 lines (187 loc) 6.19 kB
import { useCallback, useEffect, useMemo, useState } from "react" import { useTargetNetwork } from "./useTargetNetwork" import { Abi, AbiEvent, ExtractAbiEventNames } from "abitype" import { useInterval } from "usehooks-ts" import { Hash } from "viem" import * as chains from "viem/chains" import { usePublicClient } from "wagmi" import { useDeployedContractInfo } from "~~/hooks/xchange-create" import { replacer } from "~~/utils/xchange/common" import { ContractAbi, ContractName, UseEventHistoryConfig, UseEventHistoryData, } from "~~/utils/xchange/contract" import xchangeConfig from "~~/xchange.config" /** * Reads events from a deployed contract * @param config - The config settings * @param config.contractName - deployed contract name * @param config.eventName - name of the event to listen for * @param config.fromBlock - the block number to start reading events from * @param config.filters - filters to be applied to the event (parameterName: value) * @param config.blockData - if set to true it will return the block data for each event (default: false) * @param config.transactionData - if set to true it will return the transaction data for each event (default: false) * @param config.receiptData - if set to true it will return the receipt data for each event (default: false) * @param config.watch - if set to true, the events will be updated every pollingInterval milliseconds set at xchangeConfig (default: false) * @param config.enabled - set this to false to disable the hook from running (default: true) */ export const useEventHistory = < TContractName extends ContractName, TEventName extends ExtractAbiEventNames<ContractAbi<TContractName>>, TBlockData extends boolean = false, TTransactionData extends boolean = false, TReceiptData extends boolean = false, >({ contractName, eventName, fromBlock, filters, blockData, transactionData, receiptData, watch, enabled = true, }: UseEventHistoryConfig< TContractName, TEventName, TBlockData, TTransactionData, TReceiptData >) => { const [events, setEvents] = useState<any[]>([]) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState<string>() const [fromBlockUpdated, setFromBlockUpdated] = useState<bigint>(fromBlock) const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName) const { targetNetwork } = useTargetNetwork() const publicClient = usePublicClient({ chainId: targetNetwork.id, }) const readEvents = useCallback( async () => { setIsLoading(true) try { if (!deployedContractData) { throw new Error("Contract not found") } if (!enabled) { throw new Error("Hook disabled") } if (!publicClient) { throw new Error("Public client not found") } const event = (deployedContractData.abi as Abi).find( part => part.type === "event" && part.name === eventName ) as AbiEvent const blockNumber = await publicClient.getBlockNumber({ cacheTime: 0 }) if (blockNumber >= fromBlockUpdated) { const logs = await publicClient.getLogs({ address: deployedContractData?.address, event, args: filters as any, fromBlock: fromBlockUpdated, toBlock: blockNumber, }) setFromBlockUpdated(blockNumber + 1n) const newEvents = [] for (let i = logs.length - 1; i >= 0; i--) { newEvents.push({ log: logs[i], args: logs[i].args, block: blockData && logs[i].blockHash === null ? null : await publicClient.getBlock({ blockHash: logs[i].blockHash as Hash, }), transaction: transactionData && logs[i].transactionHash !== null ? await publicClient.getTransaction({ hash: logs[i].transactionHash as Hash, }) : null, receipt: receiptData && logs[i].transactionHash !== null ? await publicClient.getTransactionReceipt({ hash: logs[i].transactionHash as Hash, }) : null, }) } setEvents([...newEvents, ...events]) setError(undefined) } } catch (e: any) { if (events.length > 0) { setEvents([]) } setError(e) console.error(e) } finally { setIsLoading(false) } }, // eslint-disable-next-line react-hooks/exhaustive-deps [ blockData, deployedContractData, enabled, eventName, events, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(filters, replacer), fromBlockUpdated, publicClient, receiptData, transactionData, ] ) useEffect(() => { if (!deployedContractLoading) { readEvents() } }, [readEvents, deployedContractLoading]) useEffect(() => { // Reset the internal state when target network or fromBlock changed setEvents([]) setFromBlockUpdated(fromBlock) setError(undefined) }, [fromBlock, targetNetwork.id]) useInterval( async () => { if (!deployedContractLoading) { readEvents() } }, watch && enabled ? targetNetwork.id !== chains.hardhat.id ? xchangeConfig.pollingInterval : 4_000 : null ) const eventHistoryData = useMemo( () => events?.map(addIndexedArgsToEvent) as UseEventHistoryData< TContractName, TEventName, TBlockData, TTransactionData, TReceiptData >, [events] ) return { data: eventHistoryData, isLoading: isLoading, error: error, } } export const addIndexedArgsToEvent = (event: any) => { if (event.args && !Array.isArray(event.args)) { return { ...event, args: { ...event.args, ...Object.values(event.args) } } } return event }