Skip to content

Commit

Permalink
doc: added docs to useGetSmartWalletTransactions
Browse files Browse the repository at this point in the history
  • Loading branch information
Jadapema committed Jan 22, 2025
1 parent dbd2767 commit b9e81e5
Showing 1 changed file with 55 additions and 6 deletions.
61 changes: 55 additions & 6 deletions src/hooks/use-get-smart-wallet-transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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<string, string | bigint>;
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);
Expand All @@ -44,6 +57,13 @@ export const useGetSmartWalletTransactions = () => {
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(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',
Expand Down Expand Up @@ -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');
Expand All @@ -104,21 +128,30 @@ 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) => {
const eventAbi = LedgerVaultAbi.abi.find(
(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<string, ReturnType<typeof parseAbiItem>>);

/**
* 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);
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -220,5 +271,3 @@ export const useGetSmartWalletTransactions = () => {

return { transactions, loading, error };
}

export default useGetSmartWalletTransactions;

0 comments on commit b9e81e5

Please sign in to comment.