Smart Contract

FlowTransactionSchedulerUtils

A.e467b9dd11fa00df.FlowTransactionSchedulerUtils

Valid From

138,787,574

Deployed

1w ago
Feb 14, 2026, 05:34:45 PM UTC

Dependents

122 imports
1import FlowTransactionScheduler from 0xe467b9dd11fa00df
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import EVM from 0xe467b9dd11fa00df
5import MetadataViews from 0x1d7e57aa55817448
6
7/// FlowTransactionSchedulerUtils provides utility functionality for working with scheduled transactions
8/// on the Flow blockchain.
9///
10/// In the future, this contract will be updated to include more functionality 
11/// to make it more convenient for working with scheduled transactions for various use cases.
12///
13access(all) contract FlowTransactionSchedulerUtils {
14
15    /// Storage path for Manager resources
16    access(all) let managerStoragePath: StoragePath
17
18    /// Public path for Manager resources
19    access(all) let managerPublicPath: PublicPath
20
21    /// Entitlements
22    access(all) entitlement Owner
23
24    /// HandlerInfo is a struct that stores information about a single transaction handler
25    /// that has been used to schedule transactions.
26    /// It is stored in the manager's handlerInfos dictionary.
27    /// It stores the type identifier of the handler, the transaction IDs that have been scheduled for it,
28    /// and a capability to the handler.
29    /// The capability is used to borrow a reference to the handler when needed.
30    /// The transaction IDs are used to track the transactions that have been scheduled for the handler.
31    /// The type identifier is used to differentiate between handlers of the same type.
32    access(all) struct HandlerInfo {
33        /// The type identifier of the handler
34        access(all) let typeIdentifier: String
35
36        /// The transaction IDs that have been scheduled for the handler
37        access(all) let transactionIDs: [UInt64]
38
39        /// The capability to the handler
40        access(contract) let capability: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>
41
42        init(typeIdentifier: String, capability: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>) {
43            self.typeIdentifier = typeIdentifier
44            self.capability = capability
45            self.transactionIDs = []
46        }
47
48        /// Add a transaction ID to the handler's transaction IDs
49        /// @param id: The ID of the transaction to add
50        access(contract) fun addTransactionID(id: UInt64) {
51            self.transactionIDs.append(id)
52        }
53
54        /// Remove a transaction ID from the handler's transaction IDs
55        /// @param id: The ID of the transaction to remove
56        access(contract) fun removeTransactionID(id: UInt64) {
57            let index = self.transactionIDs.firstIndex(of: id)
58            if index != nil {
59                self.transactionIDs.remove(at: index!)
60            }
61        }
62
63        /// Borrow an un-entitled reference to the handler
64        /// @return: A reference to the handler, or nil if not found
65        access(contract) view fun borrow(): &{FlowTransactionScheduler.TransactionHandler}? {
66            return self.capability.borrow() as? &{FlowTransactionScheduler.TransactionHandler}
67        }
68    }
69
70    /// The Manager resource offers a convenient way for users and developers to
71    /// group, schedule, cancel, and query scheduled transactions through a single resource.
72    /// The Manager is defined as an interface to allow for multiple implementations of the manager
73    /// and to support upgrades that may be needed in the future to add additional storage fields and functionality.
74    /// 
75    /// Key features:
76    /// - Organizes scheduled and executed transactions by handler type and timestamp
77    /// - Simplified scheduling interface that works with previously used transaction handlers
78    /// - Transaction tracking and querying capabilities by handler, timestamp, and ID
79    /// - Handler metadata and view resolution support
80    access(all) resource interface Manager {
81
82        /// Schedules a transaction by passing the arguments directly
83        /// to the FlowTransactionScheduler schedule function
84        /// This also should store the information about the transaction
85        /// and handler in the manager's fields
86        access(Owner) fun schedule(
87            handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
88            data: AnyStruct?,
89            timestamp: UFix64,
90            priority: FlowTransactionScheduler.Priority,
91            executionEffort: UInt64,
92            fees: @FlowToken.Vault
93        ): UInt64
94
95        /// Schedules a transaction that uses a previously used handler
96        /// This should also store the information about the transaction
97        /// and handler in the manager's fields
98        access(Owner) fun scheduleByHandler(
99            handlerTypeIdentifier: String,
100            handlerUUID: UInt64?,
101            data: AnyStruct?,
102            timestamp: UFix64,
103            priority: FlowTransactionScheduler.Priority,
104            executionEffort: UInt64,
105            fees: @FlowToken.Vault
106        ): UInt64
107
108        /// Cancels a scheduled transaction by its ID
109        /// This should also remove the information about the transaction from the manager's fields
110        access(Owner) fun cancel(id: UInt64): @FlowToken.Vault
111        
112        access(all) view fun getTransactionData(_ id: UInt64): FlowTransactionScheduler.TransactionData?
113        access(all) view fun borrowTransactionHandlerForID(_ id: UInt64): &{FlowTransactionScheduler.TransactionHandler}?
114        access(all) fun getHandlerTypeIdentifiers(): {String: [UInt64]}
115        access(all) view fun borrowHandler(handlerTypeIdentifier: String, handlerUUID: UInt64?): &{FlowTransactionScheduler.TransactionHandler}?
116        access(all) fun getHandlerViews(handlerTypeIdentifier: String, handlerUUID: UInt64?): [Type] 
117        access(all) fun resolveHandlerView(handlerTypeIdentifier: String, handlerUUID: UInt64?, viewType: Type): AnyStruct?      
118        access(all) fun getHandlerViewsFromTransactionID(_ id: UInt64): [Type]
119        access(all) fun resolveHandlerViewFromTransactionID(_ id: UInt64, viewType: Type): AnyStruct? 
120        access(all) view fun getTransactionIDs(): [UInt64]
121        access(all) view fun getTransactionIDsByHandler(handlerTypeIdentifier: String, handlerUUID: UInt64?): [UInt64]
122        access(all) view fun getTransactionIDsByTimestamp(_ timestamp: UFix64): [UInt64]
123        access(all) fun getTransactionIDsByTimestampRange(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: [UInt64]}
124        access(all) view fun getTransactionStatus(id: UInt64): FlowTransactionScheduler.Status?
125        access(all) view fun getSortedTimestamps(): FlowTransactionScheduler.SortedTimestamps
126    }
127
128    /// Manager resource is meant to provide users and developers with a simple way
129    /// to group the scheduled transactions that they own into one place to make it more
130    /// convenient to schedule/cancel transactions and get information about the transactions
131    /// that are managed.
132    /// It stores ScheduledTransaction resources in a dictionary and has other fields
133    /// to track the scheduled transactions by timestamp and handler
134    ///
135    access(all) resource ManagerV1: Manager {
136        /// Dictionary storing scheduled transactions by their ID
137        access(self) var scheduledTransactions: @{UInt64: FlowTransactionScheduler.ScheduledTransaction}
138
139        /// Sorted array of timestamps that this manager has transactions scheduled at
140        access(self) var sortedTimestamps: FlowTransactionScheduler.SortedTimestamps
141
142        /// Dictionary storing the IDs of the transactions scheduled at a given timestamp
143        access(self) let idsByTimestamp: {UFix64: [UInt64]}
144
145        /// Dictionary storing the handler UUIDs for transaction IDs
146        access(self) let handlerUUIDsByTransactionID: {UInt64: UInt64}
147
148        /// Dictionary storing the handlers that this manager has scheduled transactions for at one point
149        /// The field differentiates between handlers of the same type by their UUID because there can be multiple handlers of the same type
150        /// that perform the same functionality but maybe do it for different purposes
151        /// so it is important to differentiate between them in case the user needs to retrieve a specific handler
152        /// The metadata for each handler that potentially includes information about the handler's purpose
153        /// can be retrieved from the handler's reference via the getViews() and resolveView() functions
154        access(self) let handlerInfos: {String: {UInt64: HandlerInfo}}
155
156        init() {
157            self.scheduledTransactions <- {}
158            self.sortedTimestamps = FlowTransactionScheduler.SortedTimestamps()
159            self.idsByTimestamp = {}
160            self.handlerUUIDsByTransactionID = {}
161            self.handlerInfos = {}
162        }
163
164        /// scheduleByHandler schedules a transaction by a given handler that has been used before
165        /// @param handlerTypeIdentifier: The type identifier of the handler
166        /// @param data: Optional data to pass to the transaction when executed
167        /// @param timestamp: The timestamp when the transaction should be executed
168        /// @param priority: The priority of the transaction (High, Medium, or Low)
169        /// @param executionEffort: The execution effort for the transaction
170        /// @param fees: A FlowToken vault containing sufficient fees
171        /// @return: The ID of the scheduled transaction
172        access(Owner) fun scheduleByHandler(
173            handlerTypeIdentifier: String,
174            handlerUUID: UInt64?,
175            data: AnyStruct?,
176            timestamp: UFix64,
177            priority: FlowTransactionScheduler.Priority,
178            executionEffort: UInt64,
179            fees: @FlowToken.Vault
180        ): UInt64 {
181            pre {
182                self.handlerInfos.containsKey(handlerTypeIdentifier): "Invalid handler type identifier: Handler with type identifier \(handlerTypeIdentifier) not found in manager"
183                handlerUUID == nil || self.handlerInfos[handlerTypeIdentifier]!.containsKey(handlerUUID!): "Invalid handler UUID: Handler with type identifier \(handlerTypeIdentifier) and UUID \(handlerUUID!) not found in manager"
184            }
185            let handlers = self.handlerInfos[handlerTypeIdentifier]!
186            var id = handlerUUID
187            if handlerUUID == nil {
188                assert (
189                    handlers.keys.length == 1,
190                    message: "Invalid handler UUID: Handler with type identifier \(handlerTypeIdentifier) has more than one UUID, but no UUID was provided"
191                )
192                id = handlers.keys[0]
193            }
194            return self.schedule(handlerCap: handlers[id!]!.capability, data: data, timestamp: timestamp, priority: priority, executionEffort: executionEffort, fees: <-fees)
195        }
196
197        /// Schedule a transaction and store it in the manager's dictionary
198        /// @param handlerCap: A capability to a resource that implements the TransactionHandler interface
199        /// @param data: Optional data to pass to the transaction when executed
200        /// @param timestamp: The timestamp when the transaction should be executed
201        /// @param priority: The priority of the transaction (High, Medium, or Low)
202        /// @param executionEffort: The execution effort for the transaction
203        /// @param fees: A FlowToken vault containing sufficient fees
204        /// @return: The ID of the scheduled transaction
205        access(Owner) fun schedule(
206            handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
207            data: AnyStruct?,
208            timestamp: UFix64,
209            priority: FlowTransactionScheduler.Priority,
210            executionEffort: UInt64,
211            fees: @FlowToken.Vault
212        ): UInt64 {
213            // Clean up any stale transactions before scheduling a new one
214            self.cleanup()
215
216            // Route to the main FlowTransactionScheduler
217            let scheduledTransaction <- FlowTransactionScheduler.schedule(
218                handlerCap: handlerCap,
219                data: data,
220                timestamp: timestamp,
221                priority: priority,
222                executionEffort: executionEffort,
223                fees: <-fees
224            )
225
226            // Store the handler capability in our dictionary for later retrieval
227            let id = scheduledTransaction.id
228            let actualTimestamp = scheduledTransaction.timestamp
229            let handlerRef = handlerCap.borrow()
230                ?? panic("Invalid transaction handler: Could not borrow a reference to the transaction handler")
231            let handlerTypeIdentifier = handlerRef.getType().identifier
232            let handlerUUID = handlerRef.uuid
233
234            self.handlerUUIDsByTransactionID[id] = handlerUUID
235
236            // Store the handler capability in the handlers dictionary for later retrieval
237            if self.handlerInfos[handlerTypeIdentifier] != nil {
238                let handlers = &self.handlerInfos[handlerTypeIdentifier]! as auth(Mutate) &{UInt64: HandlerInfo}
239                if let handlerInfo = handlers[handlerUUID] {
240                    handlerInfo.addTransactionID(id: id)
241                } else {
242                    let handlerInfo = HandlerInfo(typeIdentifier: handlerTypeIdentifier, capability: handlerCap)
243                    handlerInfo.addTransactionID(id: id)
244                    handlers[handlerUUID] = handlerInfo
245                }
246            } else {
247                let handlerInfo = HandlerInfo(typeIdentifier: handlerTypeIdentifier, capability: handlerCap)
248                handlerInfo.addTransactionID(id: id)
249                let uuidDictionary: {UInt64: HandlerInfo} = {handlerUUID: handlerInfo}
250                self.handlerInfos[handlerTypeIdentifier] = uuidDictionary
251            }
252
253            // Store the transaction in the transactions dictionary
254            self.scheduledTransactions[scheduledTransaction.id] <-! scheduledTransaction
255
256            // Add the transaction to the sorted timestamps array
257            self.sortedTimestamps.add(timestamp: actualTimestamp)
258
259            // Store the transaction in the ids by timestamp dictionary
260            if self.idsByTimestamp[actualTimestamp] != nil {
261                let ids = &self.idsByTimestamp[actualTimestamp]! as auth(Mutate) &[UInt64]
262                ids.append(id)
263            } else {
264                self.idsByTimestamp[actualTimestamp] = [id]
265            }
266
267            return id
268        }
269
270        /// Cancel a scheduled transaction by its ID
271        /// @param id: The ID of the transaction to cancel
272        /// @return: A FlowToken vault containing the refunded fees
273        access(Owner) fun cancel(id: UInt64): @FlowToken.Vault {
274            // Remove the transaction from the transactions dictionary
275            let tx <- self.scheduledTransactions.remove(key: id)
276                ?? panic("Invalid ID: Transaction with ID \(id) not found in manager")
277
278            self.removeID(id: id, timestamp: tx.timestamp, handlerTypeIdentifier: tx.handlerTypeIdentifier)
279
280            // Cancel the transaction through the main scheduler
281            let refundedFees <- FlowTransactionScheduler.cancel(scheduledTx: <-tx!)
282
283            return <-refundedFees
284        }
285
286        /// Remove an ID from the manager's fields
287        /// @param id: The ID of the transaction to remove
288        /// @param timestamp: The timestamp of the transaction to remove
289        /// @param handlerTypeIdentifier: The type identifier of the handler of the transaction to remove
290        access(self) fun removeID(id: UInt64, timestamp: UFix64, handlerTypeIdentifier: String) {
291            pre {
292                self.handlerInfos.containsKey(handlerTypeIdentifier): "Invalid handler type identifier: Handler with type identifier \(handlerTypeIdentifier) not found in manager"
293            }
294
295            if self.idsByTimestamp.containsKey(timestamp) {
296                let ids = &self.idsByTimestamp[timestamp]! as auth(Mutate) &[UInt64]
297                let index = ids.firstIndex(of: id)
298                ids.remove(at: index!)
299                if ids.length == 0 {
300                    self.idsByTimestamp.remove(key: timestamp)
301                    self.sortedTimestamps.remove(timestamp: timestamp)
302                }
303            }
304
305            if let handlerUUID = self.handlerUUIDsByTransactionID.remove(key: id) {
306                // Remove the transaction ID from the handler info array
307                let handlers = &self.handlerInfos[handlerTypeIdentifier]! as auth(Mutate) &{UInt64: HandlerInfo}
308                if let handlerInfo = handlers[handlerUUID] {
309                    handlerInfo.removeTransactionID(id: id)
310                }
311            }
312        }
313
314        /// Clean up transactions that are no longer valid (return nil or Unknown status)
315        /// This removes and destroys transactions that have been executed, canceled, or are otherwise invalid
316        /// @return: The transactions that were cleaned up (removed from the manager)
317        access(Owner) fun cleanup(): [UInt64] {
318            let currentTimestamp = getCurrentBlock().timestamp
319            var transactionsToRemove: {UInt64: UFix64} = {}
320
321            let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp)
322            for timestamp in pastTimestamps {
323                let ids = self.idsByTimestamp[timestamp] ?? []
324                if ids.length == 0 {
325                    self.sortedTimestamps.remove(timestamp: timestamp)
326                    continue
327                }
328                for id in ids {
329                    let status = FlowTransactionScheduler.getStatus(id: id)
330                    if status == nil || status! != FlowTransactionScheduler.Status.Scheduled {
331                        transactionsToRemove[id] = timestamp
332                        // Need to temporarily limit the number of transactions to remove
333                        // because some managers on mainnet have already hit the limit and we need to batch them
334                        // to make sure they get cleaned up properly
335                        // This will be removed eventually
336                        if transactionsToRemove.length > 50 {
337                            break
338                        }
339                    }
340                }
341            }
342
343            // Then remove and destroy the identified transactions
344            for id in transactionsToRemove.keys {
345                if let tx <- self.scheduledTransactions.remove(key: id) {
346                    self.removeID(id: id, timestamp: transactionsToRemove[id]!, handlerTypeIdentifier: tx.handlerTypeIdentifier)
347                    destroy tx
348                }
349            }
350
351            return transactionsToRemove.keys
352        }
353
354        /// Remove a handler capability from the manager
355        /// The specified handler must not have any transactions scheduled for it
356        /// @param handlerTypeIdentifier: The type identifier of the handler
357        /// @param handlerUUID: The UUID of the handler
358        access(Owner) fun removeHandler(handlerTypeIdentifier: String, handlerUUID: UInt64?) {
359            // Make sure the handler exists
360            if let handlers = self.handlerInfos[handlerTypeIdentifier] {
361                var id = handlerUUID
362                // If no UUID is provided, there must be only one handler of the type
363                if handlerUUID == nil {
364                    if handlers.keys.length > 1 {
365                        // No-op if we don't know which UUID to remove
366                        return
367                    } else if handlers.keys.length == 0 {
368                        self.handlerInfos.remove(key: handlerTypeIdentifier)
369                        return
370                    }
371                    id = handlers.keys[0]
372                }
373                // Make sure the handler has no transactions scheduled for it
374                if let handlerInfo = handlers[id!] {
375                    if handlerInfo.transactionIDs.length > 0 {
376                        return
377                    }
378                }
379                // Remove the handler uuid from the handlers dictionary
380                handlers.remove(key: id!)
381
382                // If there are no more handlers of the type, remove the type from the handlers dictionary
383                if handlers.keys.length == 0 {
384                    self.handlerInfos.remove(key: handlerTypeIdentifier)
385                } else {
386                    self.handlerInfos[handlerTypeIdentifier] = handlers
387                }
388            }
389        }
390
391        /// Get transaction data by its ID
392        /// @param id: The ID of the transaction to retrieve
393        /// @return: The transaction data from FlowTransactionScheduler, or nil if not found
394        access(all) view fun getTransactionData(_ id: UInt64): FlowTransactionScheduler.TransactionData? {
395            if self.scheduledTransactions.containsKey(id) {
396                return FlowTransactionScheduler.getTransactionData(id: id)
397            }
398            return nil
399        }
400
401        /// Get an un-entitled reference to a transaction handler of a given ID
402        /// @param id: The ID of the transaction to retrieve
403        /// @return: A reference to the transaction handler, or nil if not found
404        access(all) view fun borrowTransactionHandlerForID(_ id: UInt64): &{FlowTransactionScheduler.TransactionHandler}? {
405            let txData = self.getTransactionData(id)
406            return txData?.borrowHandler()
407        }
408
409        /// Get all the handler type identifiers that the manager has scheduled transactions for
410        /// @return: A dictionary of all handler type identifiers and their UUIDs
411        access(all) fun getHandlerTypeIdentifiers(): {String: [UInt64]} {
412            var handlerTypeIdentifiers: {String: [UInt64]} = {}
413            for handlerTypeIdentifier in self.handlerInfos.keys {
414                let handlerUUIDs: [UInt64] = []
415                let handlerTypes = self.handlerInfos[handlerTypeIdentifier]!
416                for uuid in handlerTypes.keys {
417                    let handlerInfo = handlerTypes[uuid]!
418                    if !handlerInfo.capability.check() {
419                        continue
420                    }
421                    handlerUUIDs.append(uuid)
422                }
423                handlerTypeIdentifiers[handlerTypeIdentifier] = handlerUUIDs
424            }
425            return handlerTypeIdentifiers
426        }
427
428        /// Get an un-entitled reference to a handler by a given type identifier
429        /// @param handlerTypeIdentifier: The type identifier of the handler
430        /// @param handlerUUID: The UUID of the handler, if nil, there must be only one handler of the type, otherwise nil will be returned
431        /// @return: An un-entitled reference to the handler, or nil if not found
432        access(all) view fun borrowHandler(handlerTypeIdentifier: String, handlerUUID: UInt64?): &{FlowTransactionScheduler.TransactionHandler}? {
433            if let handlers = self.handlerInfos[handlerTypeIdentifier] {
434                if handlerUUID != nil {
435                    if let handlerInfo = handlers[handlerUUID!] {
436                        return handlerInfo.borrow()
437                    } 
438                } else if handlers.keys.length == 1 {
439                    // If no uuid is provided, we can just default to the only handler uuid
440                    return handlers[handlers.keys[0]]!.borrow()
441                }
442            }
443            return nil
444        }
445
446        /// Get all the views that a handler implements
447        /// @param handlerTypeIdentifier: The type identifier of the handler
448        /// @param handlerUUID: The UUID of the handler, if nil, there must be only one handler of the type, otherwise nil will be returned
449        /// @return: An array of all views
450        access(all) fun getHandlerViews(handlerTypeIdentifier: String, handlerUUID: UInt64?): [Type] {
451            if let handler = self.borrowHandler(handlerTypeIdentifier: handlerTypeIdentifier, handlerUUID: handlerUUID) {
452                return handler.getViews()
453            }
454            return []
455        }
456
457        /// Resolve a view for a handler by a given type identifier
458        /// @param handlerTypeIdentifier: The type identifier of the handler
459        /// @param handlerUUID: The UUID of the handler, if nil, there must be only one handler of the type, otherwise nil will be returned
460        /// @param viewType: The type of the view to resolve
461        /// @return: The resolved view, or nil if not found
462        access(all) fun resolveHandlerView(handlerTypeIdentifier: String, handlerUUID: UInt64?, viewType: Type): AnyStruct? {
463            if let handler = self.borrowHandler(handlerTypeIdentifier: handlerTypeIdentifier, handlerUUID: handlerUUID) {
464                return handler.resolveView(viewType)
465            }
466            return nil
467        }
468
469        /// Get all the views that a handler implements from a given transaction ID
470        /// @param transactionId: The ID of the transaction
471        /// @return: An array of all views
472        access(all) fun getHandlerViewsFromTransactionID(_ id: UInt64): [Type] {
473            if let handler = self.borrowTransactionHandlerForID(id) {
474                return handler.getViews()
475            }
476            return []
477        }
478
479        /// Resolve a view for a handler from a given transaction ID
480        /// @param transactionId: The ID of the transaction
481        /// @param viewType: The type of the view to resolve
482        /// @return: The resolved view, or nil if not found
483        access(all) fun resolveHandlerViewFromTransactionID(_ id: UInt64, viewType: Type): AnyStruct? {
484            if let handler = self.borrowTransactionHandlerForID(id) {
485                return handler.resolveView(viewType)
486            }
487            return nil
488        }
489
490        /// Get all transaction IDs stored in the manager
491        /// @return: An array of all transaction IDs
492        access(all) view fun getTransactionIDs(): [UInt64] {
493            return self.scheduledTransactions.keys
494        }
495
496        /// Get all transaction IDs stored in the manager by a given handler
497        /// @param handlerTypeIdentifier: The type identifier of the handler
498        /// @return: An array of all transaction IDs
499        access(all) view fun getTransactionIDsByHandler(handlerTypeIdentifier: String, handlerUUID: UInt64?): [UInt64] {
500            if let handlers = self.handlerInfos[handlerTypeIdentifier] {
501                if handlerUUID != nil {
502                    if let handlerInfo = handlers[handlerUUID!] {
503                        return handlerInfo.transactionIDs
504                    } 
505                } else if handlers.keys.length == 1 {
506                    // If no uuid is provided, we can just default to the only handler uuid
507                    return handlers[handlers.keys[0]]!.transactionIDs
508                }
509            }
510            return []
511        }
512
513        /// Get all transaction IDs stored in the manager by a given timestamp
514        /// @param timestamp: The timestamp
515        /// @return: An array of all transaction IDs
516        access(all) view fun getTransactionIDsByTimestamp(_ timestamp: UFix64): [UInt64] {
517            return self.idsByTimestamp[timestamp] ?? []
518        }
519
520        /// Get all the timestamps and IDs from a given range of timestamps
521        /// @param startTimestamp: The start timestamp
522        /// @param endTimestamp: The end timestamp
523        /// @return: A dictionary of timestamps and IDs
524        access(all) fun getTransactionIDsByTimestampRange(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: [UInt64]} {
525            var transactionsInTimeframe: {UFix64: [UInt64]} = {}
526            
527            // Validate input parameters
528            if startTimestamp > endTimestamp {
529                return transactionsInTimeframe
530            }
531            
532            // Get all timestamps that fall within the specified range
533            let allTimestampsBeforeEnd = self.sortedTimestamps.getBefore(current: endTimestamp)
534            
535            for timestamp in allTimestampsBeforeEnd {
536                // Check if this timestamp falls within our range
537                if timestamp < startTimestamp { continue }
538                
539                var timestampTransactions: [UInt64] = self.idsByTimestamp[timestamp] ?? []
540                
541                if timestampTransactions.length > 0 {
542                    transactionsInTimeframe[timestamp] = timestampTransactions
543                }
544            }
545            
546            return transactionsInTimeframe
547        }
548
549        /// Get the status of a transaction by its ID
550        /// @param id: The ID of the transaction
551        /// @return: The status of the transaction, or Status.Unknown if not found in manager
552        access(all) view fun getTransactionStatus(id: UInt64): FlowTransactionScheduler.Status? {
553            if self.scheduledTransactions.containsKey(id) {
554                return FlowTransactionScheduler.getStatus(id: id)
555            }
556            return FlowTransactionScheduler.Status.Unknown
557        }
558
559        /// Gets the sorted timestamps struct
560        /// @return: The sorted timestamps struct
561        access(all) view fun getSortedTimestamps(): FlowTransactionScheduler.SortedTimestamps {
562            return self.sortedTimestamps
563        }
564    }
565
566    /// Create a new Manager instance
567    /// @return: A new Manager resource
568    access(all) fun createManager(): @{Manager} {
569        return <-create ManagerV1()
570    }
571
572    access(all) init() {
573        self.managerStoragePath = /storage/flowTransactionSchedulerManager
574        self.managerPublicPath = /public/flowTransactionSchedulerManager
575    }
576
577    /// Get a public reference to a manager at the given address
578    /// @param address: The address of the manager
579    /// @return: A public reference to the manager
580    access(all) view fun borrowManager(at: Address): &{Manager}? {
581        return getAccount(at).capabilities.borrow<&{Manager}>(self.managerPublicPath)
582    }
583
584    /*********************************************
585    
586    COA Handler Utils
587
588    **********************************************/
589
590    access(all) event COAHandlerExecutionError(id: UInt64, owner: Address?, coaAddress: String?, errorMessage: String)
591
592    access(all) view fun coaHandlerStoragePath(): StoragePath {
593        return /storage/coaScheduledTransactionHandler
594    }
595
596    access(all) view fun coaHandlerPublicPath(): PublicPath {
597        return /public/coaScheduledTransactionHandler
598    }
599
600    /// COATransactionHandler is a resource that wraps a capability to a COA (Cadence Owned Account)
601    /// and implements the TransactionHandler interface to allow scheduling transactions for COAs.
602    /// This handler enables users to schedule transactions that will be executed on behalf of their COA.
603    access(all) resource COATransactionHandler: FlowTransactionScheduler.TransactionHandler {
604        /// The capability to the COA resource
605        access(self) let coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
606
607        /// The capability to the FlowToken vault
608        access(self) let flowTokenVaultCapability: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
609
610        init(coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>,
611             flowTokenVaultCapability: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
612        )
613        {
614            pre {
615                coaCapability.check(): "COA capability is invalid or expired"
616                flowTokenVaultCapability.check(): "FlowToken vault capability is invalid or expired"
617            }
618            self.coaCapability = coaCapability
619            self.flowTokenVaultCapability = flowTokenVaultCapability
620        }
621
622        access(self) fun emitError(id: UInt64, errorMessage: String) {
623            let coa = self.coaCapability.borrow()!
624            emit COAHandlerExecutionError(id: id, owner: self.owner?.address, coaAddress: coa.address().toString(),
625                                          errorMessage: errorMessage)
626        }
627
628        /// Execute the scheduled transaction using the COA
629        /// @param id: The ID of the scheduled transaction
630        /// @param data: Optional data passed to the transaction execution. In this case, the data must be a COAHandlerParams struct with valid values.
631        access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
632
633            // Borrow the COA capability
634            let coa = self.coaCapability.borrow()
635            if coa == nil {
636                emit COAHandlerExecutionError(id: id, owner: self.owner?.address ?? Address(0x0), coaAddress: nil,
637                                              errorMessage: "COA capability is invalid or expired for scheduled transaction with ID \(id)")
638                return
639            }
640
641            // Parse the data into a list of COAHandlerParams
642            // If the data is a single COAHandlerParams struct, wrap it in a list
643            var params: [COAHandlerParams]? = data as? [COAHandlerParams]
644            if params == nil {
645                if let param = data as? COAHandlerParams {
646                    params = [param]
647                }
648            }
649
650            // Iterate through all the COA transactions and execute them all
651            // If revertOnFailure is true for a transaction and any part of it fails, the entire scheduled transaction will be reverted
652            // If not but a part of the transaction fails, an error event will be emitted but the scheduled transaction will continue to execute the next transaction
653            //
654            if let transactions = params {
655                for index, txParams in transactions {
656                    switch txParams.txType {
657                        case COAHandlerTxType.DepositFLOW:
658                            let vault = self.flowTokenVaultCapability.borrow()
659                            if vault == nil {
660                                if !txParams.revertOnFailure {
661                                    self.emitError(id: id, errorMessage: "FlowToken vault capability is invalid or expired for scheduled transaction with ID \(id) and index \(index)")
662                                    continue
663                                } else {
664                                    panic("FlowToken vault capability is invalid or expired for scheduled transaction with ID \(id) and index \(index)")
665                                }
666                            }
667
668                            if txParams.amount! > vault!.balance && !txParams.revertOnFailure {
669                                self.emitError(id: id, errorMessage: "Insufficient FLOW in FlowToken vault for deposit into COA for scheduled transaction with ID \(id) and index \(index)")
670                                continue
671                            }
672
673                            // Deposit the FLOW into the COA vault. If there isn't enough FLOW in the vault,
674                            //the transaction will be reverted because we know revertOnFailure is true
675                            coa!.deposit(from: <-vault!.withdraw(amount: txParams.amount!) as! @FlowToken.Vault)
676                        case COAHandlerTxType.WithdrawFLOW:
677                            let vault = self.flowTokenVaultCapability.borrow()
678                            if vault == nil {
679                                if !txParams.revertOnFailure {
680                                    self.emitError(id: id, errorMessage: "FlowToken vault capability is invalid or expired for scheduled transaction with ID \(id) and index \(index)")
681                                    continue
682                                } else {
683                                    panic("FlowToken vault capability is invalid or expired for scheduled transaction with ID \(id) and index \(index)")
684                                }
685                            }
686
687                            let amount = EVM.Balance(attoflow: 0)
688                            amount.setFLOW(flow: txParams.amount!)
689
690                            if amount.attoflow > coa!.balance().attoflow && !txParams.revertOnFailure {
691                                self.emitError(id: id, errorMessage: "Insufficient FLOW in COA vault for withdrawal from COA for scheduled transaction with ID \(id) and index \(index)")
692                                continue
693                            }
694
695                            // Withdraw the FLOW from the COA vault. If there isn't enough FLOW in the COA,
696                            // the transaction will be reverted because we know revertOnFailure is true
697                            vault!.deposit(from: <-coa!.withdraw(balance: amount))
698                        case COAHandlerTxType.Call:
699                            let result = coa!.call(to: txParams.callToEVMAddress!, data: txParams.data!, gasLimit: txParams.gasLimit!, value: txParams.value!)
700
701                            if result.status != EVM.Status.successful {
702                                if !txParams.revertOnFailure {
703                                    self.emitError(id: id, errorMessage: "EVM call failed for scheduled transaction with ID \(id) and index \(index) with error: \(result.errorCode):\(result.errorMessage)")
704                                    continue
705                                } else {
706                                    panic("EVM call failed for scheduled transaction with ID \(id) and index \(index) with error: \(result.errorCode):\(result.errorMessage)")
707                                }
708                            }
709                    }
710                }
711            } else {
712                self.emitError(id: id, errorMessage: "Invalid scheduled transaction data type for COA handler execution for tx with ID \(id)! Expected [FlowTransactionSchedulerUtils.COAHandlerParams] but got \(data.getType().identifier)")
713                return
714            }
715        }
716
717        /// Get the views supported by this handler
718        /// @return: Array of view types
719        access(all) view fun getViews(): [Type] {
720            return [
721                Type<COAHandlerView>(),
722                Type<StoragePath>(),
723                Type<PublicPath>(),
724                Type<MetadataViews.Display>()
725            ]
726        }
727
728        /// Resolve a view for this handler
729        /// @param viewType: The type of view to resolve
730        /// @return: The resolved view data, or nil if not supported
731        access(all) fun resolveView(_ viewType: Type): AnyStruct? {
732            if viewType == Type<COAHandlerView>() {
733                return COAHandlerView(
734                    coaOwner: self.coaCapability.borrow()?.owner?.address,
735                    coaEVMAddress: self.coaCapability.borrow()?.address(),
736                    coaBalance: self.coaCapability.borrow()?.balance(),
737                )
738            }
739            if viewType == Type<StoragePath>() {
740                return FlowTransactionSchedulerUtils.coaHandlerStoragePath()
741            } else if viewType == Type<PublicPath>() {
742                return FlowTransactionSchedulerUtils.coaHandlerPublicPath()
743            } else if viewType == Type<MetadataViews.Display>() {
744                return MetadataViews.Display(
745                    name: "COA Scheduled Transaction Handler",
746                    description: "Scheduled Transaction Handler that can execute transactions on behalf of a COA",
747                    thumbnail: MetadataViews.HTTPFile(
748                        url: ""
749                    )
750                )
751            }
752            return nil
753        }
754    }
755
756    /// Enum for COA handler execution type
757    access(all) enum COAHandlerTxType: UInt8 {
758        access(all) case DepositFLOW
759        access(all) case WithdrawFLOW
760        access(all) case Call
761
762        // TODO: Should we have other transaction types??
763    }
764
765    access(all) struct COAHandlerParams {
766
767        /// The type of transaction to execute
768        access(all) let txType: COAHandlerTxType
769
770        /// Indicates if the whole set of scheduled transactions should be reverted
771        /// if this one transaction fails to execute in EVM
772        access(all) let revertOnFailure: Bool
773
774        /// The amount of FLOW to deposit or withdraw
775        /// Not required for the Call transaction type
776        access(all) let amount: UFix64?
777
778        /// The following fields are only required for the Call transaction type
779        access(all) let callToEVMAddress: EVM.EVMAddress?
780        access(all) let data: [UInt8]?
781        access(all) let gasLimit: UInt64?
782        access(all) let value: EVM.Balance?
783
784        init(txType: UInt8, revertOnFailure: Bool, amount: UFix64?, callToEVMAddress: String?, data: [UInt8]?, gasLimit: UInt64?, value: UInt?) {
785            self.txType = COAHandlerTxType(rawValue: txType)
786                ?? panic("Invalid COA transaction type enum")
787            self.revertOnFailure = revertOnFailure
788            if self.txType == COAHandlerTxType.DepositFLOW {
789                assert(amount != nil, message: "Amount is required for deposit but was not provided")
790            }
791            if self.txType == COAHandlerTxType.WithdrawFLOW {
792                assert(amount != nil, message: "Amount is required for withdrawal but was not provided")
793            }
794            if self.txType == COAHandlerTxType.Call {
795                assert(callToEVMAddress != nil, message: "Call to EVM address is required for EVM call but was not provided")
796                assert((data != nil && value != nil) || (data == nil ? value != nil : true), message: "Data and/or value are required for EVM call but neither were provided")
797                assert(gasLimit != nil, message: "Gas limit is required for EVM call but was not provided")
798            }
799            self.amount = amount
800            if callToEVMAddress != nil {
801                self.callToEVMAddress = EVM.addressFromString(callToEVMAddress!)
802            } else {
803                self.callToEVMAddress = nil
804            }
805            if data != nil {
806                self.data = data
807            } else {
808                self.data = []
809            }
810            self.gasLimit = gasLimit
811            if let unwrappedValue = value {
812                self.value = EVM.Balance(attoflow: unwrappedValue)
813            } else {
814                self.value = nil
815            }
816        }
817    }
818
819    /// View struct for COA handler metadata
820    access(all) struct COAHandlerView {
821        access(all) let coaOwner: Address?
822        access(all) let coaEVMAddress: EVM.EVMAddress?
823
824        access(all) let coaBalance: EVM.Balance?
825
826        init(coaOwner: Address?, coaEVMAddress: EVM.EVMAddress?, coaBalance: EVM.Balance?) {
827            self.coaOwner = coaOwner
828            self.coaEVMAddress = coaEVMAddress
829            self.coaBalance = coaBalance
830        }
831    }
832
833    /// Create a COA transaction handler
834    /// @param coaCapability: Capability to the COA resource
835    /// @param flowTokenVaultCapability: Capability to the FlowToken vault
836    /// @return: A new COATransactionHandler resource
837    access(all) fun createCOATransactionHandler(
838        coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>,
839        flowTokenVaultCapability: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
840    ): @COATransactionHandler {
841        return <-create COATransactionHandler(
842            coaCapability: coaCapability,
843            flowTokenVaultCapability: flowTokenVaultCapability,
844        )
845    }
846
847    /********************************************
848    
849    Scheduled Transactions Metadata Views
850    
851    ***********************************************/
852
853}