@@ -6,6 +6,9 @@ import { GLOBAL_CONSTANTS } from '@src/config-global.ts';
6
6
import LedgerVaultAbi from '@src/config/abi/LedgerVault.json' ;
7
7
import { addTransaction , setTransactions } from '@redux/transactions' ;
8
8
9
+ /**
10
+ * Type definition for a transaction log, including event data and relevant block/transaction metadata.
11
+ */
9
12
export type TransactionLog = {
10
13
address : string ;
11
14
args : {
@@ -29,13 +32,23 @@ export type TransactionLog = {
29
32
transactionIndex : number ;
30
33
} ;
31
34
35
+ /**
36
+ * Configuration object for each event:
37
+ * - eventName: Name of the event in the smart contract ABI.
38
+ * - args: Address-related arguments used to filter logs (e.g., recipient, origin).
39
+ * - getEventType: Function to determine a custom "event type" (e.g., transferTo, transferFrom) based on the log contents and the user's address.
40
+ */
32
41
type EventConfig = {
33
42
eventName : string ;
34
43
args : Record < string , string | bigint > ;
35
44
getEventType : ( log : any , userAddress : string ) => string ;
36
45
} ;
37
46
38
- export const useGetSmartWalletTransactions = ( ) => {
47
+ /**
48
+ * Hook to retrieve smart wallet transactions by querying logs from the LedgerVault contract.
49
+ * It also manages live updates when new events are detected in real time.
50
+ */
51
+ export default function useGetSmartWalletTransactions ( ) {
39
52
const dispatch = useDispatch ( ) ;
40
53
const sessionData = useSelector ( ( state : any ) => state . auth . session ) ;
41
54
const blockchainEvents = useSelector ( ( state : any ) => state . blockchainEvents . events ) ;
@@ -44,6 +57,13 @@ export const useGetSmartWalletTransactions = () => {
44
57
const [ loading , setLoading ] = useState < boolean > ( true ) ;
45
58
const [ error , setError ] = useState < string | null > ( null ) ;
46
59
60
+ /**
61
+ * We define all event configurations that we want to capture.
62
+ * Each configuration includes:
63
+ * - The event name.
64
+ * - An object "args" that indicates which fields in the log must match the user's address.
65
+ * - A function to map the raw event to a custom "event type" (transferFrom, transferTo, deposit, etc.).
66
+ */
47
67
const eventConfigs : EventConfig [ ] = [
48
68
{
49
69
eventName : 'FundsTransferred' ,
@@ -94,6 +114,10 @@ export const useGetSmartWalletTransactions = () => {
94
114
} ,
95
115
] ;
96
116
117
+ /**
118
+ * Helper function to create the event signature needed by viem's parseAbiItem().
119
+ * For example, an event signature looks like "event FundsTransferred(address indexed origin, address indexed recipient, ...)".
120
+ */
97
121
const createEventSignature = ( event : any ) : string => {
98
122
if ( ! event || ! event . name || ! event . inputs ) {
99
123
throw new Error ( 'Invalid event in ABI' ) ;
@@ -104,21 +128,30 @@ export const useGetSmartWalletTransactions = () => {
104
128
return `event ${ event . name } (${ inputs } )` ;
105
129
} ;
106
130
131
+ /**
132
+ * Generate a dictionary (object) of parsed ABIs based on all unique event names in the eventConfigs.
133
+ * a) Extract unique event names (e.g. FundsTransferred, FundsDeposited, etc.).
134
+ * b) Find those events in the LedgerVaultAbi and parse them with parseAbiItem().
135
+ */
107
136
const uniqueEventNames = Array . from (
108
- new Set ( eventConfigs . map ( ( config ) => config . eventName ) )
137
+ new Set ( eventConfigs . map ( ( config ) => config . eventName ) ) // Removes duplicates
109
138
) ;
110
139
111
140
const parsedAbis = uniqueEventNames . reduce ( ( acc , eventName ) => {
112
141
const eventAbi = LedgerVaultAbi . abi . find (
113
142
( item : any ) => item . type === 'event' && item . name === eventName
114
143
) ;
115
144
if ( ! eventAbi ) {
116
- throw new Error ( `The event is not fund ${ eventName } on the ABI` ) ;
145
+ throw new Error ( `No definition found for event ${ eventName } in the ABI` ) ;
117
146
}
118
147
acc [ eventName ] = parseAbiItem ( createEventSignature ( eventAbi ) ) ;
119
148
return acc ;
120
149
} , { } as Record < string , ReturnType < typeof parseAbiItem > > ) ;
121
150
151
+ /**
152
+ * Function to fetch historical logs from the LedgerVault contract, using the user's address as a filter.
153
+ * The logs are then sorted, processed, and stored in Redux.
154
+ */
122
155
const fetchLogs = async ( ) => {
123
156
if ( ! sessionData ?. address ) {
124
157
setLoading ( false ) ;
@@ -129,6 +162,7 @@ export const useGetSmartWalletTransactions = () => {
129
162
setLoading ( true ) ;
130
163
setError ( null ) ;
131
164
165
+ // a) Build an array of promises, one for each eventConfig, calling publicClient.getLogs.
132
166
const promises = eventConfigs . map ( ( { eventName, args } ) => {
133
167
return publicClient . getLogs ( {
134
168
address : GLOBAL_CONSTANTS . LEDGER_VAULT_ADDRESS as Address ,
@@ -139,14 +173,18 @@ export const useGetSmartWalletTransactions = () => {
139
173
} ) ;
140
174
} ) ;
141
175
176
+ // b) Execute all the promises in parallel.
142
177
const results = await Promise . all ( promises ) ;
143
178
179
+ // c) Flatten the array of arrays of logs into one array.
144
180
const allLogs = results . flat ( ) ;
145
181
182
+ // d) Fetch block timestamps for each log and map them to a structured format.
146
183
const logsWithDetails = await Promise . all (
147
184
allLogs . map ( async ( log : any ) => {
148
185
const block = await publicClient . getBlock ( { blockNumber : log . blockNumber } ) ;
149
186
187
+ // Find the event config to determine the custom "eventType".
150
188
const foundConfig = eventConfigs . find ( ( c ) => c . eventName === log . eventName ) ;
151
189
const eventType = foundConfig
152
190
? foundConfig . getEventType ( log , sessionData ?. address )
@@ -162,21 +200,27 @@ export const useGetSmartWalletTransactions = () => {
162
200
} )
163
201
) ;
164
202
203
+ // e) Sort logs by blockNumber descending, then by transactionIndex descending.
165
204
const sortedLogs = logsWithDetails . sort ( ( a , b ) => {
166
205
const blockDifference = Number ( b . blockNumber ) - Number ( a . blockNumber ) ;
167
206
if ( blockDifference !== 0 ) return blockDifference ;
168
207
return Number ( b . transactionIndex ) - Number ( a . transactionIndex ) ;
169
208
} ) ;
170
209
210
+ // Finally, update Redux state with the sorted logs.
171
211
dispatch ( setTransactions ( sortedLogs ) ) ;
172
212
} catch ( err ) {
173
213
console . error ( 'Error fetching logs:' , err ) ;
174
- setError ( err instanceof Error ? err . message : 'An unknown error occurred ' ) ;
214
+ setError ( err instanceof Error ? err . message : 'Unknown error' ) ;
175
215
} finally {
176
216
setLoading ( false ) ;
177
217
}
178
218
} ;
179
219
220
+ /**
221
+ * useEffect hook that fires when the user's address changes, triggering the fetchLogs function.
222
+ * If there's already a list of transactions, we can stop showing the loader.
223
+ */
180
224
useEffect ( ( ) => {
181
225
fetchLogs ( ) ;
182
226
if ( transactions . length ) {
@@ -185,10 +229,17 @@ export const useGetSmartWalletTransactions = () => {
185
229
// eslint-disable-next-line react-hooks/exhaustive-deps
186
230
} , [ sessionData ?. address ] ) ;
187
231
232
+ /**
233
+ * Real-time events handling:
234
+ * When a new event is picked up in `blockchainEvents`, we check if it's from the LedgerVault contract and
235
+ * if it's one of the recognized event names. If yes, we process it similarly (fetch block info, add extra fields)
236
+ * and then dispatch addTransaction to Redux.
237
+ */
188
238
useEffect ( ( ) => {
189
239
if ( ! blockchainEvents ?. length ) return ;
190
240
191
241
blockchainEvents . forEach ( async ( log : any ) => {
242
+ // Filter out logs not from our contract or event names not in use.
192
243
if (
193
244
log . address !== GLOBAL_CONSTANTS . LEDGER_VAULT_ADDRESS ||
194
245
! uniqueEventNames . includes ( log . eventName )
@@ -220,5 +271,3 @@ export const useGetSmartWalletTransactions = () => {
220
271
221
272
return { transactions, loading, error } ;
222
273
}
223
-
224
- export default useGetSmartWalletTransactions ;
0 commit comments