diff --git a/src/hooks/use-get-smart-wallet-transactions.ts b/src/hooks/use-get-smart-wallet-transactions.ts index 915bd767c..6de24ee9f 100644 --- a/src/hooks/use-get-smart-wallet-transactions.ts +++ b/src/hooks/use-get-smart-wallet-transactions.ts @@ -6,6 +6,9 @@ import { GLOBAL_CONSTANTS } from '@src/config-global.ts'; import LedgerVaultAbi from '@src/config/abi/LedgerVault.json'; import { addTransaction, setTransactions } from '@redux/transactions'; +/** + * Type definition for a transaction log, including event data and relevant block/transaction metadata. + */ export type TransactionLog = { address: string; args: { @@ -29,13 +32,23 @@ export type TransactionLog = { transactionIndex: number; }; +/** + * Configuration object for each event: + * - eventName: Name of the event in the smart contract ABI. + * - args: Address-related arguments used to filter logs (e.g., recipient, origin). + * - getEventType: Function to determine a custom "event type" (e.g., transferTo, transferFrom) based on the log contents and the user's address. + */ type EventConfig = { eventName: string; args: Record; getEventType: (log: any, userAddress: string) => string; }; -export const useGetSmartWalletTransactions = () => { +/** + * Hook to retrieve smart wallet transactions by querying logs from the LedgerVault contract. + * It also manages live updates when new events are detected in real time. + */ +export default function useGetSmartWalletTransactions() { const dispatch = useDispatch(); const sessionData = useSelector((state: any) => state.auth.session); const blockchainEvents = useSelector((state: any) => state.blockchainEvents.events); @@ -44,6 +57,13 @@ export const useGetSmartWalletTransactions = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + /** + * We define all event configurations that we want to capture. + * Each configuration includes: + * - The event name. + * - An object "args" that indicates which fields in the log must match the user's address. + * - A function to map the raw event to a custom "event type" (transferFrom, transferTo, deposit, etc.). + */ const eventConfigs: EventConfig[] = [ { eventName: 'FundsTransferred', @@ -94,6 +114,10 @@ export const useGetSmartWalletTransactions = () => { }, ]; + /** + * Helper function to create the event signature needed by viem's parseAbiItem(). + * For example, an event signature looks like "event FundsTransferred(address indexed origin, address indexed recipient, ...)". + */ const createEventSignature = (event: any): string => { if (!event || !event.name || !event.inputs) { throw new Error('Invalid event in ABI'); @@ -104,8 +128,13 @@ export const useGetSmartWalletTransactions = () => { return `event ${event.name}(${inputs})`; }; + /** + * Generate a dictionary (object) of parsed ABIs based on all unique event names in the eventConfigs. + * a) Extract unique event names (e.g. FundsTransferred, FundsDeposited, etc.). + * b) Find those events in the LedgerVaultAbi and parse them with parseAbiItem(). + */ const uniqueEventNames = Array.from( - new Set(eventConfigs.map((config) => config.eventName)) + new Set(eventConfigs.map((config) => config.eventName)) // Removes duplicates ); const parsedAbis = uniqueEventNames.reduce((acc, eventName) => { @@ -113,12 +142,16 @@ export const useGetSmartWalletTransactions = () => { (item: any) => item.type === 'event' && item.name === eventName ); if (!eventAbi) { - throw new Error(`The event is not fund ${eventName} on the ABI`); + throw new Error(`No definition found for event ${eventName} in the ABI`); } acc[eventName] = parseAbiItem(createEventSignature(eventAbi)); return acc; }, {} as Record>); + /** + * Function to fetch historical logs from the LedgerVault contract, using the user's address as a filter. + * The logs are then sorted, processed, and stored in Redux. + */ const fetchLogs = async () => { if (!sessionData?.address) { setLoading(false); @@ -129,6 +162,7 @@ export const useGetSmartWalletTransactions = () => { setLoading(true); setError(null); + // a) Build an array of promises, one for each eventConfig, calling publicClient.getLogs. const promises = eventConfigs.map(({ eventName, args }) => { return publicClient.getLogs({ address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address, @@ -139,14 +173,18 @@ export const useGetSmartWalletTransactions = () => { }); }); + // b) Execute all the promises in parallel. const results = await Promise.all(promises); + // c) Flatten the array of arrays of logs into one array. const allLogs = results.flat(); + // d) Fetch block timestamps for each log and map them to a structured format. const logsWithDetails = await Promise.all( allLogs.map(async (log: any) => { const block = await publicClient.getBlock({ blockNumber: log.blockNumber }); + // Find the event config to determine the custom "eventType". const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName); const eventType = foundConfig ? foundConfig.getEventType(log, sessionData?.address) @@ -162,21 +200,27 @@ export const useGetSmartWalletTransactions = () => { }) ); + // e) Sort logs by blockNumber descending, then by transactionIndex descending. const sortedLogs = logsWithDetails.sort((a, b) => { const blockDifference = Number(b.blockNumber) - Number(a.blockNumber); if (blockDifference !== 0) return blockDifference; return Number(b.transactionIndex) - Number(a.transactionIndex); }); + // Finally, update Redux state with the sorted logs. dispatch(setTransactions(sortedLogs)); } catch (err) { console.error('Error fetching logs:', err); - setError(err instanceof Error ? err.message : 'An unknown error occurred'); + setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } }; + /** + * useEffect hook that fires when the user's address changes, triggering the fetchLogs function. + * If there's already a list of transactions, we can stop showing the loader. + */ useEffect(() => { fetchLogs(); if (transactions.length) { @@ -185,10 +229,17 @@ export const useGetSmartWalletTransactions = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionData?.address]); + /** + * Real-time events handling: + * When a new event is picked up in `blockchainEvents`, we check if it's from the LedgerVault contract and + * if it's one of the recognized event names. If yes, we process it similarly (fetch block info, add extra fields) + * and then dispatch addTransaction to Redux. + */ useEffect(() => { if (!blockchainEvents?.length) return; blockchainEvents.forEach(async (log: any) => { + // Filter out logs not from our contract or event names not in use. if ( log.address !== GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS || !uniqueEventNames.includes(log.eventName) @@ -220,5 +271,3 @@ export const useGetSmartWalletTransactions = () => { return { transactions, loading, error }; } - -export default useGetSmartWalletTransactions;