DeploySEALED

◇@▓!◇@▪□■╲□╲!╱!~◇&!○@◇╱◆▫▓%╱@%◆╱▒*○◇╲▓□◆*●▓$□■*╳╳■*▓█╳~▫■?□#╳█●□

Transaction ID

Timestamp

Jan 30, 2026, 04:01:09 PM UTC
4w ago

Block Height

140,587,918

Computation

0

Execution Fee

0.0365 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
FlowTransactionSchedulerBase
1codeString
import FungibleToken from 0xf233dcee88fe0abe import FlowToken from 0x1654653399040a61 import FlowFees from 0xf919ee77447b7497 import FlowStorageFees from 0xe467b9dd11fa00df import ViewResolver from 0x1d7e57aa55817448 /// FlowTransactionScheduler enables smart contracts to schedule autonomous execution in the future. /// /// This contract implements FLIP 330's scheduled transaction system, allowing contracts to "wake up" and execute /// logic at predefined times without external triggers. /// /// Scheduled transactions are prioritized (High/Medium/Low) with different execution guarantees and fee multipliers: /// - High priority guarantees first-block execution, /// - Medium priority provides best-effort scheduling, /// - Low priority executes opportunistically when capacity allows after the time it was scheduled. /// /// The system uses time slots with execution effort limits to manage network resources, /// ensuring predictable performance while enabling novel autonomous blockchain patterns like recurring /// payments, automated arbitrage, and time-based contract logic. access(all) contract FlowTransactionSchedulerBase { /// singleton instance used to store all scheduled transaction data /// and route all scheduled transaction functionality access(self) var sharedScheduler: Capability<auth(Cancel) &SharedScheduler> /// storage path for the singleton scheduler resource access(all) let storagePath: StoragePath /// Enums /// Priority access(all) enum Priority: UInt8 { access(all) case High access(all) case Medium access(all) case Low } /// Status access(all) enum Status: UInt8 { /// unknown statuses are used for handling historic scheduled transactions with null statuses access(all) case Unknown /// mutable status access(all) case Scheduled /// finalized statuses access(all) case Executed access(all) case Canceled } /// Events /// Emitted when a transaction is scheduled access(all) event Scheduled( id: UInt64, priority: UInt8, timestamp: UFix64, executionEffort: UInt64, fees: UFix64, transactionHandlerOwner: Address, transactionHandlerTypeIdentifier: String, transactionHandlerUUID: UInt64, // The public path of the transaction handler that can be used to resolve views // DISCLAIMER: There is no guarantee that the public path is accurate transactionHandlerPublicPath: PublicPath? ) /// Emitted when a scheduled transaction's scheduled timestamp is reached and it is ready for execution access(all) event PendingExecution( id: UInt64, priority: UInt8, executionEffort: UInt64, fees: UFix64, transactionHandlerOwner: Address, transactionHandlerTypeIdentifier: String ) /// Emitted when a scheduled transaction is executed by the FVM access(all) event Executed( id: UInt64, priority: UInt8, executionEffort: UInt64, transactionHandlerOwner: Address, transactionHandlerTypeIdentifier: String, transactionHandlerUUID: UInt64, // The public path of the transaction handler that can be used to resolve views // DISCLAIMER: There is no guarantee that the public path is accurate transactionHandlerPublicPath: PublicPath? ) /// Emitted when a scheduled transaction is canceled by the creator of the transaction access(all) event Canceled( id: UInt64, priority: UInt8, feesReturned: UFix64, feesDeducted: UFix64, transactionHandlerOwner: Address, transactionHandlerTypeIdentifier: String ) /// Emitted when a collection limit is reached /// The limit that was reached is non-nil and is the limit that was reached /// The other limit that was not reached is nil access(all) event CollectionLimitReached( collectionEffortLimit: UInt64?, collectionTransactionsLimit: Int? ) /// Emitted when the limit on the number of transactions that can be removed in process() is reached access(all) event RemovalLimitReached() // Emitted when one or more of the configuration details fields are updated // Event listeners can listen to this and query the new configuration // if they need to access(all) event ConfigUpdated() // Emitted when a critical issue is encountered access(all) event CriticalIssue(message: String) /// Entitlements access(all) entitlement Execute access(all) entitlement Process access(all) entitlement Cancel access(all) entitlement UpdateConfig /// Interfaces /// TransactionHandler is an interface that defines a single method executeTransaction that /// must be implemented by the resource that contains the logic to be executed by the scheduled transaction. /// An authorized capability to this resource is provided when scheduling a transaction. /// The transaction scheduler uses this capability to execute the transaction when its scheduled timestamp arrives. access(all) resource interface TransactionHandler: ViewResolver.Resolver { access(all) view fun getViews(): [Type] { return [] } access(all) fun resolveView(_ view: Type): AnyStruct? { return nil } /// Executes the implemented transaction logic /// /// @param id: The id of the scheduled transaction (this can be useful for any internal tracking) /// @param data: The data that was passed when the transaction was originally scheduled /// that may be useful for the execution of the transaction logic access(Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) } /// Structs /// ScheduledTransaction is the resource that the user receives after scheduling a transaction. /// It allows them to get the status of their transaction and can be passed back /// to the scheduler contract to cancel the transaction if it has not yet been executed. access(all) resource ScheduledTransaction { access(all) let id: UInt64 access(all) let timestamp: UFix64 access(all) let handlerTypeIdentifier: String access(all) view fun status(): Status? { return FlowTransactionSchedulerBase.sharedScheduler.borrow()!.getStatus(id: self.id) } init( id: UInt64, timestamp: UFix64, handlerTypeIdentifier: String ) { self.id = id self.timestamp = timestamp self.handlerTypeIdentifier = handlerTypeIdentifier } // event emitted when the resource is destroyed access(all) event ResourceDestroyed(id: UInt64 = self.id, timestamp: UFix64 = self.timestamp, handlerTypeIdentifier: String = self.handlerTypeIdentifier) } /// EstimatedScheduledTransaction contains data for estimating transaction scheduling. access(all) struct EstimatedScheduledTransaction { /// flowFee is the estimated fee in Flow for the transaction to be scheduled access(all) let flowFee: UFix64? /// timestamp is estimated timestamp that the transaction will be executed at access(all) let timestamp: UFix64? /// error is an optional error message if the transaction cannot be scheduled access(all) let error: String? access(contract) view init(flowFee: UFix64?, timestamp: UFix64?, error: String?) { self.flowFee = flowFee self.timestamp = timestamp self.error = error } } /// Transaction data is a representation of a scheduled transaction /// It is the source of truth for an individual transaction and stores the /// capability to the handler that contains the logic that will be executed by the transaction. access(all) struct TransactionData { access(all) let id: UInt64 access(all) let priority: Priority access(all) let executionEffort: UInt64 access(all) var status: Status /// Fee amount to pay for the transaction access(all) let fees: UFix64 /// The timestamp that the transaction is scheduled for /// For medium priority transactions, it may be different than the requested timestamp /// For low priority transactions, it is the requested timestamp, /// but the timestamp where the transaction is actually executed may be different access(all) var scheduledTimestamp: UFix64 /// Capability to the logic that the transaction will execute access(contract) let handler: Capability<auth(Execute) &{TransactionHandler}> /// Type identifier of the transaction handler access(all) let handlerTypeIdentifier: String access(all) let handlerAddress: Address /// Optional data that can be passed to the handler /// This data is publicly accessible, so make sure it does not contain /// any privileged information or functionality access(contract) let data: AnyStruct? access(contract) init( id: UInt64, handler: Capability<auth(Execute) &{TransactionHandler}>, scheduledTimestamp: UFix64, data: AnyStruct?, priority: Priority, executionEffort: UInt64, fees: UFix64, ) { self.id = id self.handler = handler self.data = data self.priority = priority self.executionEffort = executionEffort self.fees = fees self.status = Status.Scheduled let handlerRef = handler.borrow() ?? panic("Invalid transaction handler: Could not borrow a reference to the transaction handler") self.handlerAddress = handler.address self.handlerTypeIdentifier = handlerRef.getType().identifier self.scheduledTimestamp = scheduledTimestamp } /// setStatus updates the status of the transaction. /// It panics if the transaction status is already finalized. access(contract) fun setStatus(newStatus: Status) { pre { newStatus != Status.Unknown: "Invalid status: New status cannot be Unknown" self.status != Status.Executed && self.status != Status.Canceled: "Invalid status: Transaction with id \(self.id) is already finalized" newStatus == Status.Executed ? self.status == Status.Scheduled : true: "Invalid status: Transaction with id \(self.id) can only be set as Executed if it is Scheduled" newStatus == Status.Canceled ? self.status == Status.Scheduled : true: "Invalid status: Transaction with id \(self.id) can only be set as Canceled if it is Scheduled" } self.status = newStatus } /// setScheduledTimestamp updates the scheduled timestamp of the transaction. /// It panics if the transaction status is already finalized. access(contract) fun setScheduledTimestamp(newTimestamp: UFix64) { pre { self.status != Status.Executed && self.status != Status.Canceled: "Invalid status: Transaction with id \(self.id) is already finalized" } self.scheduledTimestamp = newTimestamp } /// payAndRefundFees withdraws fees from the transaction based on the refund multiplier. /// It deposits any leftover fees to the FlowFees vault to be used to pay node operator rewards /// like any other transaction on the Flow network. access(contract) fun payAndRefundFees(refundMultiplier: UFix64): @FlowToken.Vault { pre { refundMultiplier >= 0.0 && refundMultiplier <= 1.0: "Invalid refund multiplier: The multiplier must be between 0.0 and 1.0 but got \(refundMultiplier)" } if refundMultiplier == 0.0 { FlowFees.deposit(from: <-FlowTransactionSchedulerBase.withdrawFees(amount: self.fees)) return <-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) } else { let amountToReturn = self.fees * refundMultiplier let amountToKeep = self.fees - amountToReturn let feesToReturn <- FlowTransactionSchedulerBase.withdrawFees(amount: amountToReturn) FlowFees.deposit(from: <-FlowTransactionSchedulerBase.withdrawFees(amount: amountToKeep)) return <-feesToReturn } } /// getData copies and returns the data field access(all) view fun getData(): AnyStruct? { return self.data } /// borrowHandler returns an un-entitled reference to the transaction handler /// This allows users to query metadata views about the handler /// @return: An un-entitled reference to the transaction handler access(all) view fun borrowHandler(): &{TransactionHandler} { return self.handler.borrow() as? &{TransactionHandler} ?? panic("Invalid transaction handler: Could not borrow a reference to the transaction handler") } } /// Struct interface representing all the base configuration details in the Scheduler contract /// that is used for governing the protocol /// This is an interface to allow for the configuration details to be updated in the future access(all) struct interface SchedulerConfig { /// maximum effort that can be used for any transaction access(all) var maximumIndividualEffort: UInt64 /// minimum execution effort is the minimum effort that can be /// used for any transaction access(all) var minimumExecutionEffort: UInt64 /// slot total effort limit is the maximum effort that can be /// cumulatively allocated to one timeslot by all priorities access(all) var slotTotalEffortLimit: UInt64 /// slot shared effort limit is the maximum effort /// that can be allocated to high and medium priority /// transactions combined after their exclusive effort reserves have been filled access(all) var slotSharedEffortLimit: UInt64 /// priority effort reserve is the amount of effort that is /// reserved exclusively for each priority access(all) var priorityEffortReserve: {Priority: UInt64} /// priority effort limit is the maximum cumulative effort per priority in a timeslot access(all) var priorityEffortLimit: {Priority: UInt64} /// max data size is the maximum data size that can be stored for a transaction access(all) var maxDataSizeMB: UFix64 /// priority fee multipliers are values we use to calculate the added /// processing fee for each priority access(all) var priorityFeeMultipliers: {Priority: UFix64} /// refund multiplier is the portion of the fees that are refunded when any transaction is cancelled access(all) var refundMultiplier: UFix64 /// canceledTransactionsLimit is the maximum number of canceled transactions /// to keep in the canceledTransactions array access(all) var canceledTransactionsLimit: UInt /// collectionEffortLimit is the maximum effort that can be used for all transactions in a collection access(all) var collectionEffortLimit: UInt64 /// collectionTransactionsLimit is the maximum number of transactions that can be processed in a collection access(all) var collectionTransactionsLimit: Int access(all) init( maximumIndividualEffort: UInt64, minimumExecutionEffort: UInt64, slotSharedEffortLimit: UInt64, priorityEffortReserve: {Priority: UInt64}, lowPriorityEffortLimit: UInt64, maxDataSizeMB: UFix64, priorityFeeMultipliers: {Priority: UFix64}, refundMultiplier: UFix64, canceledTransactionsLimit: UInt, collectionEffortLimit: UInt64, collectionTransactionsLimit: Int, txRemovalLimit: UInt ) { post { self.refundMultiplier >= 0.0 && self.refundMultiplier <= 1.0: "Invalid refund multiplier: The multiplier must be between 0.0 and 1.0 but got \(refundMultiplier)" self.priorityFeeMultipliers[Priority.Low]! >= 1.0: "Invalid priority fee multiplier: Low priority multiplier must be greater than or equal to 1.0 but got \(self.priorityFeeMultipliers[Priority.Low]!)" self.priorityFeeMultipliers[Priority.Medium]! > self.priorityFeeMultipliers[Priority.Low]!: "Invalid priority fee multiplier: Medium priority multiplier must be greater than or equal to \(priorityFeeMultipliers[Priority.Low]!) but got \(priorityFeeMultipliers[Priority.Medium]!)" self.priorityFeeMultipliers[Priority.High]! > self.priorityFeeMultipliers[Priority.Medium]!: "Invalid priority fee multiplier: High priority multiplier must be greater than or equal to \(priorityFeeMultipliers[Priority.Medium]!) but got \(priorityFeeMultipliers[Priority.High]!)" self.priorityEffortLimit[Priority.High]! >= self.priorityEffortReserve[Priority.High]!: "Invalid priority effort limit: High priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.High]!)" self.priorityEffortLimit[Priority.Medium]! >= self.priorityEffortReserve[Priority.Medium]!: "Invalid priority effort limit: Medium priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.Medium]!)" self.priorityEffortLimit[Priority.Low]! >= self.priorityEffortReserve[Priority.Low]!: "Invalid priority effort limit: Low priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.Low]!)" self.priorityEffortReserve[Priority.Low]! == 0: "Invalid priority effort reserve: Low priority effort reserve must be 0" self.collectionTransactionsLimit >= 0: "Invalid collection transactions limit: Collection transactions limit must be greater than or equal to 0 but got \(collectionTransactionsLimit)" self.canceledTransactionsLimit >= 1: "Invalid canceled transactions limit: Canceled transactions limit must be greater than or equal to 1 but got \(canceledTransactionsLimit)" self.collectionEffortLimit > self.slotTotalEffortLimit: "Invalid collection effort limit: Collection effort limit must be greater than \(self.slotTotalEffortLimit) but got \(self.collectionEffortLimit)" } } access(all) view fun getTxRemovalLimit(): UInt } /// Concrete implementation of the SchedulerConfig interface /// This struct is used to store the configuration details in the Scheduler contract access(all) struct Config: SchedulerConfig { access(all) var maximumIndividualEffort: UInt64 access(all) var minimumExecutionEffort: UInt64 access(all) var slotTotalEffortLimit: UInt64 access(all) var slotSharedEffortLimit: UInt64 access(all) var priorityEffortReserve: {Priority: UInt64} access(all) var priorityEffortLimit: {Priority: UInt64} access(all) var maxDataSizeMB: UFix64 access(all) var priorityFeeMultipliers: {Priority: UFix64} access(all) var refundMultiplier: UFix64 access(all) var canceledTransactionsLimit: UInt access(all) var collectionEffortLimit: UInt64 access(all) var collectionTransactionsLimit: Int access(all) init( maximumIndividualEffort: UInt64, minimumExecutionEffort: UInt64, slotSharedEffortLimit: UInt64, priorityEffortReserve: {Priority: UInt64}, lowPriorityEffortLimit: UInt64, maxDataSizeMB: UFix64, priorityFeeMultipliers: {Priority: UFix64}, refundMultiplier: UFix64, canceledTransactionsLimit: UInt, collectionEffortLimit: UInt64, collectionTransactionsLimit: Int, txRemovalLimit: UInt ) { self.maximumIndividualEffort = maximumIndividualEffort self.minimumExecutionEffort = minimumExecutionEffort self.slotTotalEffortLimit = slotSharedEffortLimit + priorityEffortReserve[Priority.High]! + priorityEffortReserve[Priority.Medium]! self.slotSharedEffortLimit = slotSharedEffortLimit self.priorityEffortReserve = priorityEffortReserve self.priorityEffortLimit = { Priority.High: priorityEffortReserve[Priority.High]! + slotSharedEffortLimit, Priority.Medium: priorityEffortReserve[Priority.Medium]! + slotSharedEffortLimit, Priority.Low: lowPriorityEffortLimit } self.maxDataSizeMB = maxDataSizeMB self.priorityFeeMultipliers = priorityFeeMultipliers self.refundMultiplier = refundMultiplier self.canceledTransactionsLimit = canceledTransactionsLimit self.collectionEffortLimit = collectionEffortLimit self.collectionTransactionsLimit = collectionTransactionsLimit } access(all) view fun getTxRemovalLimit(): UInt { return FlowTransactionSchedulerBase.account.storage.copy<UInt>(from: /storage/txRemovalLimit) ?? 200 } } /// SortedTimestamps maintains timestamps sorted in ascending order for efficient processing /// It encapsulates all operations related to maintaining and querying sorted timestamps access(all) struct SortedTimestamps { /// Internal sorted array of timestamps access(self) var timestamps: [UFix64] access(all) init() { self.timestamps = [] } /// Add a timestamp to the sorted array maintaining sorted order access(all) fun add(timestamp: UFix64) { var insertIndex = 0 for i, ts in self.timestamps { if timestamp < ts { insertIndex = i break } else if timestamp == ts { return } insertIndex = i + 1 } self.timestamps.insert(at: insertIndex, timestamp) } /// Remove a timestamp from the sorted array access(all) fun remove(timestamp: UFix64) { let index = self.timestamps.firstIndex(of: timestamp) if index != nil { self.timestamps.remove(at: index!) } } /// Get all timestamps that are in the past (less than or equal to current timestamp) access(all) fun getBefore(current: UFix64): [UFix64] { let pastTimestamps: [UFix64] = [] for timestamp in self.timestamps { if timestamp <= current { pastTimestamps.append(timestamp) } else { break // No need to check further since array is sorted } } return pastTimestamps } /// Check if there are any timestamps that need processing /// Returns true if processing is needed, false for early exit access(all) fun hasBefore(current: UFix64): Bool { return self.timestamps.length > 0 && self.timestamps[0] <= current } /// Get the whole array of timestamps access(all) fun getAll(): [UFix64] { return self.timestamps } } /// Resources /// Shared scheduler is a resource that is used as a singleton in the scheduler contract and contains /// all the functionality to schedule, process and execute transactions as well as the internal state. access(all) resource SharedScheduler { /// nextID contains the next transaction ID to be assigned /// This the ID is monotonically increasing and is used to identify each transaction access(contract) var nextID: UInt64 /// transactions is a map of transaction IDs to TransactionData structs access(contract) var transactions: {UInt64: TransactionData} /// slot queue is a map of timestamps to Priorities to transaction IDs and their execution efforts access(contract) var slotQueue: {UFix64: {Priority: {UInt64: UInt64}}} /// slot used effort is a map of timestamps map of priorities and /// efforts that has been used for the timeslot access(contract) var slotUsedEffort: {UFix64: {Priority: UInt64}} /// sorted timestamps manager for efficient processing access(contract) var sortedTimestamps: SortedTimestamps /// canceled transactions keeps a record of canceled transaction IDs up to a canceledTransactionsLimit access(contract) var canceledTransactions: [UInt64] /// Struct that contains all the configuration details for the transaction scheduler protocol /// Can be updated by the owner of the contract access(contract) var config: {SchedulerConfig} access(all) init() { self.nextID = 1 self.canceledTransactions = [0 as UInt64] self.transactions = {} self.slotUsedEffort = {} self.slotQueue = {} self.sortedTimestamps = SortedTimestamps() /* Default slot efforts and limits look like this: Timestamp Slot (17.5kee) ┌─────────────────────────┐ │ ┌─────────────┐ │ │ │ High Only │ │ High: 15kee max │ │ 10kee │ │ (10 exclusive + 5 shared) │ └─────────────┘ │ | ┌───────────────┐ | │ | Shared Pool │ | | │ (High+Medium) │ | | │ 5kee │ | | └───────────────┘ | │ ┌─────────────┐ │ Medium: 7.5kee max │ │ Medium Only │ │ (2.5 exclusive + 5 shared) │ │ 2.5kee │ │ │ └─────────────┘ │ │ ┌─────────────────────┐ │ Low: 2.5kee max │ │ Low (if space left) │ │ (execution time only) │ │ 2.5kee │ │ │ └─────────────────────┘ │ └─────────────────────────┘ */ let sharedEffortLimit: UInt64 = 5_000 let highPriorityEffortReserve: UInt64 = 10_000 let mediumPriorityEffortReserve: UInt64 = 2_500 self.config = Config( maximumIndividualEffort: 9999, minimumExecutionEffort: 100, slotSharedEffortLimit: sharedEffortLimit, priorityEffortReserve: { Priority.High: highPriorityEffortReserve, Priority.Medium: mediumPriorityEffortReserve, Priority.Low: 0 }, lowPriorityEffortLimit: 2_500, maxDataSizeMB: 0.001, priorityFeeMultipliers: { Priority.High: 10.0, Priority.Medium: 5.0, Priority.Low: 2.0 }, refundMultiplier: 0.5, canceledTransactionsLimit: 1000, collectionEffortLimit: 500_000, // Maximum effort for all transactions in a collection collectionTransactionsLimit: 150, // Maximum number of transactions in a collection txRemovalLimit: 200 ) } /// Gets a copy of the struct containing all the configuration details /// of the Scheduler resource access(contract) view fun getConfig(): {SchedulerConfig} { return self.config } /// sets all the configuration details for the Scheduler resource access(UpdateConfig) fun setConfig(newConfig: {SchedulerConfig}, txRemovalLimit: UInt) { self.config = newConfig FlowTransactionSchedulerBase.account.storage.load<UInt>(from: /storage/txRemovalLimit) FlowTransactionSchedulerBase.account.storage.save(txRemovalLimit, to: /storage/txRemovalLimit) emit ConfigUpdated() } /// getTransaction returns a copy of the specified transaction access(contract) view fun getTransaction(id: UInt64): TransactionData? { return self.transactions[id] } /// borrowTransaction borrows a reference to the specified transaction access(contract) view fun borrowTransaction(id: UInt64): &TransactionData? { return &self.transactions[id] } /// getCanceledTransactions returns a copy of the canceled transactions array access(contract) view fun getCanceledTransactions(): [UInt64] { return self.canceledTransactions } /// getTransactionsForTimeframe returns a dictionary of transactions scheduled within a specified time range, /// organized by timestamp and priority with arrays of transaction IDs. /// WARNING: If you provide a time range that is too large, the function will likely fail to complete /// because the function will run out of gas. Keep the time range small. /// /// @param startTimestamp: The start timestamp (inclusive) for the time range /// @param endTimestamp: The end timestamp (inclusive) for the time range /// @return {UFix64: {Priority: [UInt64]}}: A dictionary mapping timestamps to priorities to arrays of transaction IDs access(contract) fun getTransactionsForTimeframe(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { var transactionsInTimeframe: {UFix64: {UInt8: [UInt64]}} = {} // Validate input parameters if startTimestamp > endTimestamp { return transactionsInTimeframe } // Get all timestamps that fall within the specified range let allTimestampsBeforeEnd = self.sortedTimestamps.getBefore(current: endTimestamp) for timestamp in allTimestampsBeforeEnd { // Check if this timestamp falls within our range if timestamp < startTimestamp { continue } let transactionPriorities = self.slotQueue[timestamp] ?? {} var timestampTransactions: {UInt8: [UInt64]} = {} for priority in transactionPriorities.keys { let transactionIDs = transactionPriorities[priority] ?? {} var priorityTransactions: [UInt64] = [] for id in transactionIDs.keys { priorityTransactions.append(id) } if priorityTransactions.length > 0 { timestampTransactions[priority.rawValue] = priorityTransactions } } if timestampTransactions.keys.length > 0 { transactionsInTimeframe[timestamp] = timestampTransactions } } return transactionsInTimeframe } /// calculate fee by converting execution effort to a fee in Flow tokens. /// @param executionEffort: The execution effort of the transaction /// @param priority: The priority of the transaction /// @param dataSizeMB: The size of the data that was passed when the transaction was originally scheduled /// @return UFix64: The fee in Flow tokens that is required to pay for the transaction access(contract) fun calculateFee(executionEffort: UInt64, priority: Priority, dataSizeMB: UFix64): UFix64 { // Use the official FlowFees calculation let baseFee = FlowFees.computeFees(inclusionEffort: 1.0, executionEffort: UFix64(executionEffort)/100000000.0) // Scale the execution fee by the multiplier for the priority let scaledExecutionFee = baseFee * self.config.priorityFeeMultipliers[priority]! // Calculate the FLOW required to pay for storage of the transaction data let storageFee = FlowStorageFees.storageCapacityToFlow(dataSizeMB) // Add inclusion Flow fee for scheduled transactions let inclusionFee = 0.00001 return scaledExecutionFee + storageFee + inclusionFee } /// getNextIDAndIncrement returns the next ID and increments the ID counter access(self) fun getNextIDAndIncrement(): UInt64 { let nextID = self.nextID self.nextID = self.nextID + 1 return nextID } /// get status of the scheduled transaction /// @param id: The ID of the transaction to get the status of /// @return Status: The status of the transaction, if the transaction is not found Unknown is returned. access(contract) view fun getStatus(id: UInt64): Status? { // if the transaction ID is greater than the next ID, it is not scheduled yet and has never existed if id == 0 as UInt64 || id >= self.nextID { return nil } // This should always return Scheduled or Executed if let tx = self.borrowTransaction(id: id) { return tx.status } // if the transaction was canceled and it is still not pruned from // list return canceled status if self.canceledTransactions.contains(id) { return Status.Canceled } // if transaction ID is after first canceled ID it must be executed // otherwise it would have been canceled and part of this list let firstCanceledID = self.canceledTransactions[0] if id > firstCanceledID { return Status.Executed } // the transaction list was pruned and the transaction status might be // either canceled or execute so we return unknown return Status.Unknown } /// schedule is the primary entry point for scheduling a new transaction within the scheduler contract. /// If scheduling the transaction is not possible either due to invalid arguments or due to /// unavailable slots, the function panics. // /// The schedule function accepts the following arguments: /// @param: transaction: A capability to a resource in storage that implements the transaction handler /// interface. This handler will be invoked at execution time and will receive the specified data payload. /// @param: timestamp: Specifies the earliest block timestamp at which the transaction is eligible for execution /// (Unix timestamp so fractional seconds values are ignored). It must be set in the future. /// @param: priority: An enum value (`High`, `Medium`, or `Low`) that influences the scheduling behavior and determines /// how soon after the timestamp the transaction will be executed. /// @param: executionEffort: Defines the maximum computational resources allocated to the transaction. This also determines /// the fee charged. Unused execution effort is not refunded. /// @param: fees: A Vault resource containing sufficient funds to cover the required execution effort. access(contract) fun schedule( handlerCap: Capability<auth(Execute) &{TransactionHandler}>, data: AnyStruct?, timestamp: UFix64, priority: Priority, executionEffort: UInt64, fees: @FlowToken.Vault ): @ScheduledTransaction { // Use the estimate function to validate inputs let estimate = self.estimate( data: data, timestamp: timestamp, priority: priority, executionEffort: executionEffort ) // Estimate returns an error for low priority transactions // so need to check that the error is fine // because low priority transactions are allowed in schedule if estimate.error != nil && estimate.timestamp == nil { panic(estimate.error!) } assert ( fees.balance >= estimate.flowFee!, message: "Insufficient fees: The Fee balance of \(fees.balance) is not sufficient to pay the required amount of \(estimate.flowFee!) for execution of the transaction." ) let transactionID = self.getNextIDAndIncrement() let transactionData = TransactionData( id: transactionID, handler: handlerCap, scheduledTimestamp: estimate.timestamp!, data: data, priority: priority, executionEffort: executionEffort, fees: fees.balance, ) // Deposit the fees to the service account's vault FlowTransactionSchedulerBase.depositFees(from: <-fees) let handlerRef = handlerCap.borrow() ?? panic("Invalid transaction handler: Could not borrow a reference to the transaction handler") let handlerPublicPath = handlerRef.resolveView(Type<PublicPath>()) as? PublicPath emit Scheduled( id: transactionData.id, priority: transactionData.priority.rawValue, timestamp: transactionData.scheduledTimestamp, executionEffort: transactionData.executionEffort, fees: transactionData.fees, transactionHandlerOwner: transactionData.handler.address, transactionHandlerTypeIdentifier: transactionData.handlerTypeIdentifier, transactionHandlerUUID: handlerRef.uuid, transactionHandlerPublicPath: handlerPublicPath ) // Add the transaction to the slot queue and update the internal state self.addTransaction(slot: estimate.timestamp!, txData: transactionData) return <-create ScheduledTransaction( id: transactionID, timestamp: estimate.timestamp!, handlerTypeIdentifier: transactionData.handlerTypeIdentifier ) } /// The estimate function calculates the required fee in Flow and expected execution timestamp for /// a transaction based on the requested timestamp, priority, and execution effort. // /// If the provided arguments are invalid or the transaction cannot be scheduled (e.g., due to /// insufficient computation effort or unavailable time slots) the estimate function /// returns an EstimatedScheduledTransaction struct with a non-nil error message. /// /// This helps developers ensure sufficient funding and preview the expected scheduling window, /// reducing the risk of unnecessary cancellations. /// /// @param data: The data that was passed when the transaction was originally scheduled /// @param timestamp: The requested timestamp for the transaction /// @param priority: The priority of the transaction /// @param executionEffort: The execution effort of the transaction /// @return EstimatedScheduledTransaction: A struct containing the estimated fee, timestamp, and error message access(contract) fun estimate( data: AnyStruct?, timestamp: UFix64, priority: Priority, executionEffort: UInt64 ): EstimatedScheduledTransaction { // Remove fractional values from the timestamp let sanitizedTimestamp = UFix64(UInt64(timestamp)) if sanitizedTimestamp <= getCurrentBlock().timestamp { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid timestamp: \(sanitizedTimestamp) is in the past, current timestamp: \(getCurrentBlock().timestamp)" ) } if executionEffort > self.config.maximumIndividualEffort { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid execution effort: \(executionEffort) is greater than the maximum transaction effort of \(self.config.maximumIndividualEffort)" ) } if executionEffort > self.config.priorityEffortLimit[priority]! { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid execution effort: \(executionEffort) is greater than the priority's max effort of \(self.config.priorityEffortLimit[priority]!)" ) } if executionEffort < self.config.minimumExecutionEffort { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid execution effort: \(executionEffort) is less than the minimum execution effort of \(self.config.minimumExecutionEffort)" ) } let dataSizeMB = FlowTransactionSchedulerBase.getSizeOfData(data) if dataSizeMB > self.config.maxDataSizeMB { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid data size: \(dataSizeMB) is greater than the maximum data size of \(self.config.maxDataSizeMB)MB" ) } let fee = self.calculateFee(executionEffort: executionEffort, priority: priority, dataSizeMB: dataSizeMB) let scheduledTimestamp = self.calculateScheduledTimestamp( timestamp: sanitizedTimestamp, priority: priority, executionEffort: executionEffort ) if scheduledTimestamp == nil { return EstimatedScheduledTransaction( flowFee: nil, timestamp: nil, error: "Invalid execution effort: \(executionEffort) is greater than the priority's available effort for the requested timestamp." ) } if priority == Priority.Low { return EstimatedScheduledTransaction( flowFee: fee, timestamp: scheduledTimestamp, error: "Invalid Priority: Cannot estimate for Low Priority transactions. They will be included in the first block with available space after their requested timestamp." ) } return EstimatedScheduledTransaction(flowFee: fee, timestamp: scheduledTimestamp, error: nil) } /// calculateScheduledTimestamp calculates the timestamp at which a transaction /// can be scheduled. It takes into account the priority of the transaction and /// the execution effort. /// - If the transaction is high priority, it returns the timestamp if there is enough /// space or nil if there is no space left. /// - If the transaction is medium or low priority and there is space left in the requested timestamp, /// it returns the requested timestamp. If there is not enough space, it finds the next timestamp with space. /// /// @param timestamp: The requested timestamp for the transaction /// @param priority: The priority of the transaction /// @param executionEffort: The execution effort of the transaction /// @return UFix64?: The timestamp at which the transaction can be scheduled, or nil if there is no space left for a high priority transaction access(contract) view fun calculateScheduledTimestamp( timestamp: UFix64, priority: Priority, executionEffort: UInt64 ): UFix64? { var timestampToSearch = timestamp // If no available timestamps are found, this will eventually reach the gas limit and fail // This is extremely unlikely while true { let used = self.slotUsedEffort[timestampToSearch] // if nothing is scheduled at this timestamp, we can schedule at provided timestamp if used == nil { return timestampToSearch } let available = self.getSlotAvailableEffort(timestamp: timestampToSearch, priority: priority) // if theres enough space, we can tentatively schedule at provided timestamp if executionEffort <= available { return timestampToSearch } if priority == Priority.High { // high priority demands scheduling at exact timestamp or failing return nil } timestampToSearch = timestampToSearch + 1.0 } // should never happen return nil } /// slot available effort returns the amount of effort that is available for a given timestamp and priority. access(contract) view fun getSlotAvailableEffort(timestamp: UFix64, priority: Priority): UInt64 { // Remove fractional values from the timestamp let sanitizedTimestamp = UFix64(UInt64(timestamp)) // Get the theoretical maximum allowed for the priority including shared let priorityLimit = self.config.priorityEffortLimit[priority]! // If nothing has been claimed for the requested timestamp, // return the full amount if !self.slotUsedEffort.containsKey(sanitizedTimestamp) { return priorityLimit } // Get the mapping of how much effort has been used // for each priority for the timestamp let slotPriorityEffortsUsed = self.slotUsedEffort[sanitizedTimestamp]! // Get the exclusive reserves for each priority let highReserve = self.config.priorityEffortReserve[Priority.High]! let mediumReserve = self.config.priorityEffortReserve[Priority.Medium]! // Get how much effort has been used for each priority let highUsed = slotPriorityEffortsUsed[Priority.High] ?? 0 let mediumUsed = slotPriorityEffortsUsed[Priority.Medium] ?? 0 // If it is low priority, return whatever effort is remaining // under the low priority effort limit, subtracting the currently used effort for low priority if priority == Priority.Low { let highPlusMediumUsed = highUsed + mediumUsed let totalEffortRemaining = self.config.slotTotalEffortLimit.saturatingSubtract(highPlusMediumUsed) let lowEffortRemaining = totalEffortRemaining < priorityLimit ? totalEffortRemaining : priorityLimit let lowUsed = slotPriorityEffortsUsed[Priority.Low] ?? 0 return lowEffortRemaining.saturatingSubtract(lowUsed) } // Get how much shared effort has been used for each priority // Ensure the results are always zero or positive let highSharedUsed: UInt64 = highUsed.saturatingSubtract(highReserve) let mediumSharedUsed: UInt64 = mediumUsed.saturatingSubtract(mediumReserve) // Get the theoretical total shared amount between priorities let totalShared = (self.config.slotTotalEffortLimit.saturatingSubtract(highReserve)).saturatingSubtract(mediumReserve) // Get the amount of shared effort currently available let highPlusMediumSharedUsed = highSharedUsed + mediumSharedUsed // prevent underflow let sharedAvailable = totalShared.saturatingSubtract(highPlusMediumSharedUsed) // we calculate available by calculating available shared effort and // adding any unused reserves for that priority let reserve = self.config.priorityEffortReserve[priority]! let used = slotPriorityEffortsUsed[priority] ?? 0 let unusedReserve: UInt64 = reserve.saturatingSubtract(used) let available = sharedAvailable + unusedReserve return available } /// add transaction to the queue and updates all the internal state as well as emit an event access(self) fun addTransaction(slot: UFix64, txData: TransactionData) { // If nothing is in the queue for this slot, initialize the slot if self.slotQueue[slot] == nil { self.slotQueue[slot] = {} // This also means that the used effort record for this slot has not been initialized self.slotUsedEffort[slot] = { Priority.High: 0, Priority.Medium: 0, Priority.Low: 0 } self.sortedTimestamps.add(timestamp: slot) } // Add this transaction id to the slot let slotQueue = self.slotQueue[slot]! if let priorityQueue = slotQueue[txData.priority] { priorityQueue[txData.id] = txData.executionEffort slotQueue[txData.priority] = priorityQueue } else { slotQueue[txData.priority] = { txData.id: txData.executionEffort } } self.slotQueue[slot] = slotQueue // Add the execution effort for this transaction to the total for the slot's priority let slotEfforts = self.slotUsedEffort[slot]! var newPriorityEffort = slotEfforts[txData.priority]! + txData.executionEffort slotEfforts[txData.priority] = newPriorityEffort var newTotalEffort: UInt64 = 0 for priority in slotEfforts.keys { newTotalEffort = newTotalEffort.saturatingAdd(slotEfforts[priority]!) } self.slotUsedEffort[slot] = slotEfforts // Need to potentially reschedule low priority transactions to make room for the new transaction // Iterate through them and record which ones to reschedule until the total effort is less than the limit let lowTransactionsToReschedule: [UInt64] = [] if newTotalEffort > self.config.slotTotalEffortLimit { let lowPriorityTransactions = slotQueue[Priority.Low]! for id in lowPriorityTransactions.keys { if newTotalEffort <= self.config.slotTotalEffortLimit { break } lowTransactionsToReschedule.append(id) newTotalEffort = newTotalEffort.saturatingSubtract(lowPriorityTransactions[id]!) } } // Store the transaction in the transactions map self.transactions[txData.id] = txData // Reschedule low priority transactions if needed self.rescheduleLowPriorityTransactions(slot: slot, transactions: lowTransactionsToReschedule) } /// rescheduleLowPriorityTransactions reschedules low priority transactions to make room for a new transaction /// @param slot: The slot that the transactions are currently scheduled at /// @param transactions: The transactions to reschedule access(self) fun rescheduleLowPriorityTransactions(slot: UFix64, transactions: [UInt64]) { for id in transactions { let tx = self.borrowTransaction(id: id) if tx == nil { emit CriticalIssue(message: "Invalid ID: \(id) transaction not found while rescheduling low priority transactions") continue } if tx!.priority != Priority.Low { emit CriticalIssue(message: "Invalid Priority: Cannot reschedule transaction with id \(id) because it is not low priority") continue } if tx!.scheduledTimestamp != slot { emit CriticalIssue(message: "Invalid Timestamp: Cannot reschedule transaction with id \(id) because it is not scheduled at the same slot as the new transaction") continue } let newTimestamp = self.calculateScheduledTimestamp( timestamp: slot + 1.0, priority: Priority.Low, executionEffort: tx!.executionEffort )! let effort = tx!.executionEffort let transactionData = self.removeTransaction(txData: tx!) // Subtract the execution effort for this transaction from the slot's priority let slotEfforts = self.slotUsedEffort[slot]! slotEfforts[Priority.Low] = slotEfforts[Priority.Low]!.saturatingSubtract(effort) self.slotUsedEffort[slot] = slotEfforts // Update the transaction's scheduled timestamp and add it back to the slot queue transactionData.setScheduledTimestamp(newTimestamp: newTimestamp) self.addTransaction(slot: newTimestamp, txData: transactionData) } } /// remove the transaction from the slot queue. access(self) fun removeTransaction(txData: &TransactionData): TransactionData { let transactionID = txData.id let slot = txData.scheduledTimestamp let transactionPriority = txData.priority // remove transaction object let transactionObject = self.transactions.remove(key: transactionID)! // garbage collect slots if let transactionQueue = self.slotQueue[slot] { if let priorityQueue = transactionQueue[transactionPriority] { priorityQueue[transactionID] = nil if priorityQueue.keys.length == 0 { transactionQueue.remove(key: transactionPriority) } else { transactionQueue[transactionPriority] = priorityQueue } self.slotQueue[slot] = transactionQueue } // if the slot is now empty remove it from the maps if transactionQueue.keys.length == 0 { self.slotQueue.remove(key: slot) self.slotUsedEffort.remove(key: slot) self.sortedTimestamps.remove(timestamp: slot) } } return transactionObject } /// pendingQueue creates a list of transactions that are ready for execution. /// For transaction to be ready for execution it must be scheduled. /// /// The queue is sorted by timestamp and then by priority (high, medium, low). /// The queue will contain transactions from all timestamps that are in the past. /// Low priority transactions will only be added if there is effort available in the slot. /// The return value can be empty if there are no transactions ready for execution. access(Process) fun pendingQueue(): [&TransactionData] { let currentTimestamp = getCurrentBlock().timestamp var pendingTransactions: [&TransactionData] = [] // total effort across different timestamps guards collection being over the effort limit var collectionAvailableEffort = self.config.collectionEffortLimit var transactionsAvailableCount = self.config.collectionTransactionsLimit // Collect past timestamps efficiently from sorted array let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp) for timestamp in pastTimestamps { let transactionPriorities = self.slotQueue[timestamp] ?? {} var high: [&TransactionData] = [] var medium: [&TransactionData] = [] var low: [&TransactionData] = [] for priority in transactionPriorities.keys { let transactionIDs = transactionPriorities[priority] ?? {} for id in transactionIDs.keys { let tx = self.borrowTransaction(id: id) if tx == nil { emit CriticalIssue(message: "Invalid ID: \(id) transaction not found while preparing pending queue") continue } // Only add scheduled transactions to the queue if tx!.status != Status.Scheduled { continue } // this is safeguard to prevent collection growing too large in case of block production slowdown if tx!.executionEffort >= collectionAvailableEffort || transactionsAvailableCount == 0 { emit CollectionLimitReached( collectionEffortLimit: transactionsAvailableCount == 0 ? nil : self.config.collectionEffortLimit, collectionTransactionsLimit: transactionsAvailableCount == 0 ? self.config.collectionTransactionsLimit : nil ) break } collectionAvailableEffort = collectionAvailableEffort.saturatingSubtract(tx!.executionEffort) transactionsAvailableCount = transactionsAvailableCount - 1 switch tx!.priority { case Priority.High: high.append(tx!) case Priority.Medium: medium.append(tx!) case Priority.Low: low.append(tx!) } } } pendingTransactions = pendingTransactions .concat(high) .concat(medium) .concat(low) } return pendingTransactions } /// removeExecutedTransactions removes all transactions that are marked as executed. access(self) fun removeExecutedTransactions(_ currentTimestamp: UFix64) { let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp) var numRemoved = 0 let removalLimit = self.config.getTxRemovalLimit() for timestamp in pastTimestamps { let transactionPriorities = self.slotQueue[timestamp] ?? {} for priority in transactionPriorities.keys { let transactionIDs = transactionPriorities[priority] ?? {} for id in transactionIDs.keys { numRemoved = numRemoved + 1 if UInt(numRemoved) >= removalLimit { emit RemovalLimitReached() return } let tx = self.borrowTransaction(id: id) if tx == nil { emit CriticalIssue(message: "Invalid ID: \(id) transaction not found while removing executed transactions") continue } // Only remove executed transactions if tx!.status != Status.Executed { continue } // charge the full fee for transaction execution destroy tx!.payAndRefundFees(refundMultiplier: 0.0) self.removeTransaction(txData: tx!) } } } } /// process scheduled transactions and prepare them for execution. /// /// First, it removes transactions that have already been executed. /// Then, it iterates over past timestamps in the queue and processes the transactions that are /// eligible for execution. It also emits an event for each transaction that is processed. /// /// This function is only called by the FVM to process transactions. access(Process) fun process() { let currentTimestamp = getCurrentBlock().timestamp // Early exit if no timestamps need processing if !self.sortedTimestamps.hasBefore(current: currentTimestamp) { return } self.removeExecutedTransactions(currentTimestamp) let pendingTransactions = self.pendingQueue() for tx in pendingTransactions { // Only emit the pending execution event if the transaction handler capability is borrowable // This is to prevent a situation where the transaction handler is not available // In that case, the transaction is no longer valid because it cannot be executed if let transactionHandler = tx.handler.borrow() { emit PendingExecution( id: tx.id, priority: tx.priority.rawValue, executionEffort: tx.executionEffort, fees: tx.fees, transactionHandlerOwner: tx.handler.address, transactionHandlerTypeIdentifier: transactionHandler.getType().identifier ) } // after pending execution event is emitted we set the transaction as executed because we // must rely on execution node to actually execute it. Execution of the transaction is // done in a separate transaction that calls executeTransaction(id) function. // Executing the transaction can not update the status of transaction or any other shared state, // since that blocks concurrent transaction execution. // Therefore an optimistic update to executed is made here to avoid race condition. tx.setStatus(newStatus: Status.Executed) } } /// cancel a scheduled transaction and return a portion of the fees that were paid. /// /// @param id: The ID of the transaction to cancel /// @return: The fees to be returned to the caller access(Cancel) fun cancel(id: UInt64): @FlowToken.Vault { let tx = self.borrowTransaction(id: id) ?? panic("Invalid ID: \(id) transaction not found") assert( tx.status == Status.Scheduled, message: "Transaction must be in a scheduled state in order to be canceled" ) // Subtract the execution effort for this transaction from the slot's priority let slotEfforts = self.slotUsedEffort[tx.scheduledTimestamp]! slotEfforts[tx.priority] = slotEfforts[tx.priority]!.saturatingSubtract(tx.executionEffort) self.slotUsedEffort[tx.scheduledTimestamp] = slotEfforts let totalFees = tx.fees let refundedFees <- tx.payAndRefundFees(refundMultiplier: self.config.refundMultiplier) // if the transaction was canceled, add it to the canceled transactions array // maintain sorted order by inserting at the correct position var insertIndex = 0 for i, canceledID in self.canceledTransactions { if id < canceledID { insertIndex = i break } insertIndex = i + 1 } self.canceledTransactions.insert(at: insertIndex, id) // keep the array under the limit if UInt(self.canceledTransactions.length) > self.config.canceledTransactionsLimit { self.canceledTransactions.remove(at: 0) } emit Canceled( id: tx.id, priority: tx.priority.rawValue, feesReturned: refundedFees.balance, feesDeducted: totalFees - refundedFees.balance, transactionHandlerOwner: tx.handler.address, transactionHandlerTypeIdentifier: tx.handlerTypeIdentifier ) self.removeTransaction(txData: tx) return <-refundedFees } /// execute transaction is a system function that is called by FVM to execute a transaction by ID. /// The transaction must be found and in correct state or the function panics and this is a fatal error /// /// This function is only called by the FVM to execute transactions. /// WARNING: this function should not change any shared state, it will be run concurrently and it must not be blocking. access(Execute) fun executeTransaction(id: UInt64) { let tx = self.borrowTransaction(id: id) ?? panic("Invalid ID: Transaction with id \(id) not found") assert ( tx.status == Status.Executed, message: "Invalid ID: Cannot execute transaction with id \(id) because it has incorrect status \(tx.status.rawValue)" ) let transactionHandler = tx.handler.borrow() ?? panic("Invalid transaction handler: Could not borrow a reference to the transaction handler") let handlerPublicPath = transactionHandler.resolveView(Type<PublicPath>()) as? PublicPath emit Executed( id: tx.id, priority: tx.priority.rawValue, executionEffort: tx.executionEffort, transactionHandlerOwner: tx.handler.address, transactionHandlerTypeIdentifier: transactionHandler.getType().identifier, transactionHandlerUUID: transactionHandler.uuid, transactionHandlerPublicPath: handlerPublicPath ) transactionHandler.executeTransaction(id: id, data: tx.getData()) } } /// Deposit fees to this contract's account's vault access(contract) fun depositFees(from: @FlowToken.Vault) { let vaultRef = self.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("Unable to borrow reference to the default token vault") vaultRef.deposit(from: <-from) } /// Withdraw fees from this contract's account's vault access(contract) fun withdrawFees(amount: UFix64): @FlowToken.Vault { let vaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("Unable to borrow reference to the default token vault") return <-vaultRef.withdraw(amount: amount) as! @FlowToken.Vault } access(all) fun schedule( handlerCap: Capability<auth(Execute) &{TransactionHandler}>, data: AnyStruct?, timestamp: UFix64, priority: Priority, executionEffort: UInt64, fees: @FlowToken.Vault ): @ScheduledTransaction { return <-self.sharedScheduler.borrow()!.schedule( handlerCap: handlerCap, data: data, timestamp: timestamp, priority: priority, executionEffort: executionEffort, fees: <-fees ) } access(all) fun estimate( data: AnyStruct?, timestamp: UFix64, priority: Priority, executionEffort: UInt64 ): EstimatedScheduledTransaction { return self.sharedScheduler.borrow()! .estimate( data: data, timestamp: timestamp, priority: priority, executionEffort: executionEffort, ) } access(all) fun cancel(scheduledTx: @ScheduledTransaction): @FlowToken.Vault { let id = scheduledTx.id destroy scheduledTx return <-self.sharedScheduler.borrow()!.cancel(id: id) } /// getTransactionData returns the transaction data for a given ID /// This function can only get the data for a transaction that is currently scheduled or pending execution /// because finalized transaction metadata is not stored in the contract /// @param id: The ID of the transaction to get the data for /// @return: The transaction data for the given ID access(all) view fun getTransactionData(id: UInt64): TransactionData? { return self.sharedScheduler.borrow()!.getTransaction(id: id) } /// borrowHandlerForID returns an un-entitled reference to the transaction handler for a given ID /// The handler reference can be used to resolve views to get info about the handler and see where it is stored /// @param id: The ID of the transaction to get the handler for /// @return: An un-entitled reference to the transaction handler for the given ID access(all) view fun borrowHandlerForID(_ id: UInt64): &{TransactionHandler}? { return self.getTransactionData(id: id)?.borrowHandler() } /// getCanceledTransactions returns the IDs of the transactions that have been canceled /// @return: The IDs of the transactions that have been canceled access(all) view fun getCanceledTransactions(): [UInt64] { return self.sharedScheduler.borrow()!.getCanceledTransactions() } access(all) view fun getStatus(id: UInt64): Status? { return self.sharedScheduler.borrow()!.getStatus(id: id) } /// getTransactionsForTimeframe returns the IDs of the transactions that are scheduled for a given timeframe /// @param startTimestamp: The start timestamp to get the IDs for /// @param endTimestamp: The end timestamp to get the IDs for /// @return: The IDs of the transactions that are scheduled for the given timeframe access(all) fun getTransactionsForTimeframe(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { return self.sharedScheduler.borrow()!.getTransactionsForTimeframe(startTimestamp: startTimestamp, endTimestamp: endTimestamp) } access(all) view fun getSlotAvailableEffort(timestamp: UFix64, priority: Priority): UInt64 { return self.sharedScheduler.borrow()!.getSlotAvailableEffort(timestamp: timestamp, priority: priority) } access(all) fun getConfig(): {SchedulerConfig} { return self.sharedScheduler.borrow()!.getConfig() } /// getSizeOfData takes a transaction's data /// argument and stores it in the contract account's storage, /// checking storage used before and after to see how large the data is in MB /// If data is nil, the function returns 0.0 access(all) fun getSizeOfData(_ data: AnyStruct?): UFix64 { if data == nil { return 0.0 } else { let type = data!.getType() if type.isSubtype(of: Type<Number>()) || type.isSubtype(of: Type<Bool>()) || type.isSubtype(of: Type<Address>()) || type.isSubtype(of: Type<Character>()) || type.isSubtype(of: Type<Capability>()) { return 0.0 } } let storagePath = /storage/dataTemp let storageUsedBefore = self.account.storage.used self.account.storage.save(data!, to: storagePath) let storageUsedAfter = self.account.storage.used self.account.storage.load<AnyStruct>(from: storagePath) return FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes(storageUsedAfter.saturatingSubtract(storageUsedBefore)) } access(all) init() { self.storagePath = /storage/sharedScheduler let scheduler <- create SharedScheduler() let oldScheduler <- self.account.storage.load<@AnyResource>(from: self.storagePath) destroy oldScheduler self.account.storage.save(<-scheduler, to: self.storagePath) self.sharedScheduler = self.account.capabilities.storage .issue<auth(Cancel) &SharedScheduler>(self.storagePath) } }

Cadence Script

1transaction(name: String, code: String ) {
2		prepare(signer: auth(AddContract) &Account) {
3			signer.contracts.add(name: name, code: code.utf8 )
4		}
5	}