Skip to content

Commit

Permalink
feat: added new events to transactions table ('locked', 'claimed', 'r…
Browse files Browse the repository at this point in the history
…eserved', 'collected', 'released')
  • Loading branch information
Jadapema committed Jan 22, 2025
1 parent cadae73 commit dbd2767
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 173 deletions.
32 changes: 5 additions & 27 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,43 +158,21 @@ const AppContent = () => {
return results;
};

/**
* Deposit Deposited Verde Fuente, monto, TX.
* Transfer From Transfer from Verde Origen, monto, TX.
* Transfer To Transfer to Rojo Destino, monto, TX.
* Withdraw To Withdraw to Rojo Destino, monto, TX.
* Reserve To Reserved Amarillo Propósito, monto, TX.
* Collect From Collected Verde Origen, monto, TX.
* Locked Locked Azul Propósito, monto, TX.
* Release Released Verde Destino (si aplica), monto, TX.
* Claim Claimed Verde Propósito/acuerdo, monto, TX.
*/

useEffect(() => {
if (!sessionData?.address) return;

// @TODO Add the rest of the events
// Analiazed taking care of the user as the recipient or origin
const newEvents = [
{ name: 'FundsDeposited', args: { recipient: sessionData?.address }, logText: 'Deposited' },
{ name: 'FundsTransferFrom', args: { origin: sessionData?.address }, logText: 'Transfer from' },
{ name: 'FundsTransferTo', args: { recipient: sessionData?.address }, logText: 'Transfer to' },
{ name: 'FundsWithdrawTo', args: { recipient: sessionData?.address }, logText: 'Withdraw to' },
{ name: 'FundsReserved', args: { recipient: sessionData?.address }, logText: 'Reserved' },
{ name: 'FundsCollected', args: { origin: sessionData?.address }, logText: 'Collected' },
{ name: 'FundsLocked', args: { recipient: sessionData?.address }, logText: 'Locked' },
{ name: 'FundsReleased', args: { origin: sessionData?.address }, logText: 'Released' },
{ name: 'FundsClaimed', args: { recipient: sessionData?.address }, logText: 'Claimed' },
]

const events = [
{ name: 'FundsDeposited', args: { recipient: sessionData?.address }, logText: 'New deposit (user as recipient):' },
{ name: 'FundsWithdrawn', args: { origin: sessionData?.address }, logText: 'New withdraw (user as origin):' },
{ name: 'FundsTransferred', args: { origin: sessionData?.address }, logText: 'New transfer from me:' },
{ name: 'FundsTransferred', args: { recipient: sessionData?.address }, logText: 'New transfer to me:' },
{ name: 'FundsLocked', args: { account: sessionData?.address }, logText: 'New funds locked:' },
{ name: 'FundsClaimed', args: { claimer: sessionData?.address }, logText: 'New funds claimed:' },
{ name: 'FundsReserved', args: { from: sessionData?.address }, logText: 'New funds reserved:' },
{ name: 'FundsCollected', args: { from: sessionData?.address }, logText: 'New funds collected:' },
{ name: 'FundsReleased', args: { to: sessionData?.address }, logText: 'New funds released:' },
];


const unwatchers = events.map(event => watchEvent(event.name, event.args, event.logText));

return () => {
Expand Down
232 changes: 113 additions & 119 deletions src/hooks/use-get-smart-wallet-transactions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { parseAbiItem, formatUnits, Address } from 'viem';
import { useDispatch, useSelector } from 'react-redux';
import { parseAbiItem, formatUnits, Address } from 'viem';
import { publicClient } from '@src/clients/viem/publicClient.ts';
import { GLOBAL_CONSTANTS } from '@src/config-global.ts';
import LedgerVaultAbi from '@src/config/abi/LedgerVault.json';
Expand Down Expand Up @@ -29,17 +29,96 @@ export type TransactionLog = {
transactionIndex: number;
};

const useGetSmartWalletTransactions = () => {
type EventConfig = {
eventName: string;
args: Record<string, string | bigint>;
getEventType: (log: any, userAddress: string) => string;
};

export const useGetSmartWalletTransactions = () => {
const dispatch = useDispatch();
const sessionData = useSelector((state: any) => state.auth.session);
const blockchainEvents = useSelector((state: any) => state.blockchainEvents.events);
const transactions = useSelector((state: any) => state.transactions.transactions);

// Local states for loading and error
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);

// Function to fetch historical logs
const eventConfigs: EventConfig[] = [
{
eventName: 'FundsTransferred',
args: { recipient: sessionData?.address || '' },
getEventType: (log, userAddress) =>
log.args.origin === userAddress ? 'transferTo' : 'transferFrom',
},
{
eventName: 'FundsTransferred',
args: { origin: sessionData?.address || '' },
getEventType: (log, userAddress) =>
log.args.origin === userAddress ? 'transferTo' : 'transferFrom',
},
{
eventName: 'FundsDeposited',
args: { recipient: sessionData?.address || '' },
getEventType: () => 'deposit',
},
{
eventName: 'FundsWithdrawn',
args: { origin: sessionData?.address || '' },
getEventType: () => 'withdraw',
},
{
eventName: 'FundsLocked',
args: { account: sessionData?.address || '' },
getEventType: () => 'locked',
},
{
eventName: 'FundsClaimed',
args: { claimer: sessionData?.address || '' },
getEventType: () => 'claimed',
},
{
eventName: 'FundsReserved',
args: { from: sessionData?.address || '' },
getEventType: () => 'reserved',
},
{
eventName: 'FundsCollected',
args: { from: sessionData?.address || '' },
getEventType: () => 'collected',
},
{
eventName: 'FundsReleased',
args: { to: sessionData?.address || '' },
getEventType: () => 'released',
},
];

const createEventSignature = (event: any): string => {
if (!event || !event.name || !event.inputs) {
throw new Error('Invalid event in ABI');
}
const inputs = event.inputs
.map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`)
.join(', ');
return `event ${event.name}(${inputs})`;
};

const uniqueEventNames = Array.from(
new Set(eventConfigs.map((config) => config.eventName))
);

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`);
}
acc[eventName] = parseAbiItem(createEventSignature(eventAbi));
return acc;
}, {} as Record<string, ReturnType<typeof parseAbiItem>>);

const fetchLogs = async () => {
if (!sessionData?.address) {
setLoading(false);
Expand All @@ -50,102 +129,45 @@ const useGetSmartWalletTransactions = () => {
setLoading(true);
setError(null);

// Define ABI for events to monitor
const eventsAbi = {
FundsTransferred: parseAbiItem(
createEventSignature(
LedgerVaultAbi.abi.find(
(item: any) => item.type === 'event' && item.name === 'FundsTransferred'
)
)
),
FundsDeposited: parseAbiItem(
createEventSignature(
LedgerVaultAbi.abi.find(
(item: any) => item.type === 'event' && item.name === 'FundsDeposited'
)
)
),
FundsWithdrawn: parseAbiItem(
createEventSignature(
LedgerVaultAbi.abi.find(
(item: any) => item.type === 'event' && item.name === 'FundsWithdrawn'
)
)
),
};

// Fetch logs for each event
const [transfersToMe, transfersFromMe, deposits, withdraws] = await Promise.all([
publicClient.getLogs({
address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
event: eventsAbi.FundsTransferred as any,
args: { recipient: sessionData.address },
fromBlock: 0n,
toBlock: 'latest',
}),
publicClient.getLogs({
address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
event: eventsAbi.FundsTransferred as any,
args: { origin: sessionData.address },
fromBlock: 0n,
toBlock: 'latest',
}),
publicClient.getLogs({
const promises = eventConfigs.map(({ eventName, args }) => {
return publicClient.getLogs({
address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
event: eventsAbi.FundsDeposited as any,
args: { recipient: sessionData.address },
event: parsedAbis[eventName] as any,
args,
fromBlock: 0n,
toBlock: 'latest',
}),
publicClient.getLogs({
address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
event: eventsAbi.FundsWithdrawn as any,
args: { origin: sessionData.address },
fromBlock: 0n,
toBlock: 'latest',
}),
]);
});
});

const results = await Promise.all(promises);

const allLogs = [...transfersToMe, ...transfersFromMe, ...deposits, ...withdraws];
const allLogs = results.flat();

// Add timestamps and format details for each log
const logsWithDetails = await Promise.all(
allLogs.map(async (log: any) => {
const block = await publicClient.getBlock({ blockNumber: log.blockNumber });

// Determine the event type
const event = (() => {
switch (log.eventName) {
case 'FundsTransferred':
return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom';
case 'FundsDeposited':
return 'deposit';
case 'FundsWithdrawn':
return 'withdraw';
default:
return 'unknown';
}
})();
const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName);
const eventType = foundConfig
? foundConfig.getEventType(log, sessionData?.address)
: 'unknown';

return {
...log,
timestamp: block.timestamp, // UNIX timestamp of the block
readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date
formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether
event,
timestamp: block.timestamp,
readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(),
formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0',
event: eventType,
};
})
);

// Sort logs by block number and transaction index
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);
});

// Dispatch the setTransactions action to store the logs in Redux
dispatch(setTransactions(sortedLogs));
} catch (err) {
console.error('Error fetching logs:', err);
Expand All @@ -155,76 +177,48 @@ const useGetSmartWalletTransactions = () => {
}
};

// Helper function to create event signatures
const createEventSignature = (event: any): string => {
if (!event || !event.name || !event.inputs) {
throw new Error('Invalid event in ABI');
}
const inputs = event.inputs
.map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`)
.join(', ');
return `event ${event.name}(${inputs})`;
};

// Effect to fetch historical logs when the address changes
useEffect(() => {
fetchLogs();

// Disable loader if data is already available
if (transactions.length) {
setLoading(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionData?.address]);

// Effect to handle real-time events from blockchainEvents
useEffect(() => {
if (!blockchainEvents || blockchainEvents.length === 0) return;
if (!blockchainEvents?.length) return;

// Iterate over new blockchain events
blockchainEvents.forEach(async (log: any) => {
// Process only relevant events
if (
log.address !== GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS ||
!['FundsTransferred', 'FundsDeposited', 'FundsWithdrawn'].includes(log.eventName)
!uniqueEventNames.includes(log.eventName)
) {
return;
}

try {
const block = await publicClient.getBlock({ blockNumber: log.blockNumber });
const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName);
const eventType = foundConfig
? foundConfig.getEventType(log, sessionData?.address)
: 'unknown';

// Determine the event type
const event = (() => {
switch (log.eventName) {
case 'FundsTransferred':
return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom';
case 'FundsDeposited':
return 'deposit';
case 'FundsWithdrawn':
return 'withdraw';
default:
return 'unknown';
}
})();

// Create a formatted transaction log
const formattedLog = {
...log,
timestamp: block.timestamp, // UNIX timestamp of the block
readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date
formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether
event,
timestamp: block.timestamp,
readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(),
formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0',
event: eventType,
};

// Dispatch the addTransaction action to add the new log to Redux
dispatch(addTransaction(formattedLog));
} catch (err) {
console.error('Error processing real-time log:', err);
}
});
}, [blockchainEvents, sessionData?.address, dispatch]);
}, [blockchainEvents, sessionData?.address, dispatch, uniqueEventNames, eventConfigs]);

return { transactions, loading, error };
};
}

export default useGetSmartWalletTransactions;
Loading

0 comments on commit dbd2767

Please sign in to comment.