DeploySEALED

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

Transaction ID

Timestamp

Dec 19, 2025, 09:46:49 AM UTC
2mo ago

Block Height

136,510,246

Computation

0

Execution Fee

0.0065 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
FlowCron
1codeString
import FlowTransactionScheduler from 0xe467b9dd11fa00df import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df import FlowCronUtils from 0x6dec6e64a13b881e import FlowToken from 0x1654653399040a61 import FungibleToken from 0xf233dcee88fe0abe import ViewResolver from 0x1d7e57aa55817448 import MetadataViews from 0x1d7e57aa55817448 /// FlowCron: Wraps any TransactionHandler with cron scheduling. /// /// FEATURES: /// - Dual-mode architecture (Keeper/Executor) for fault isolation /// - Keeper mode: Pure scheduling logic (only scheduling) /// - Executor mode: Runs user code (isolated failures) /// - Offset execution: First tick runs both together, subsequent ticks have 1s keeper offset /// - User schedules both executor and keeper for first tick /// - Standard cron syntax (5-field expressions) /// /// LIFECYCLE: /// 1. Create: FlowCron.createCronHandler(expression, wrappedCap) /// 2. Bootstrap: User schedules both executor and keeper for next cron tick /// 3. Execute: At each cron tick, TWO transactions run: /// - Keeper: Schedules next cycle (both keeper + executor) /// - Executor: Runs user code /// 4. Forever: Perpetual execution at every cron tick /// 5. Stop: Cancel all scheduled transactions /// /// FAULT TOLERANCE: /// - Executor failure is isolated (emits event, keeper continues) /// - Keeper failure panics with detailed error (prevents silent death) /// - System survives wrapped handler panics/failures access(all) contract FlowCron { /// Fixed priority for keeper operations /// Medium priority ensures reliable scheduling without slot filling issues access(all) let keeperPriority: FlowTransactionScheduler.Priority /// Offset in seconds for keeper scheduling relative to executor /// Essential for being scheduled after executor to prevent collision at T+1 access(all) let keeperOffset: UInt64 /// Emitted when keeper successfully schedules next cycle access(all) event CronKeeperExecuted( txID: UInt64, nextExecutorTxID: UInt64?, nextKeeperTxID: UInt64, nextExecutorTime: UInt64?, nextKeeperTime: UInt64, cronExpression: String, handlerUUID: UInt64, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) /// Emitted when executor successfully completes user code access(all) event CronExecutorExecuted( txID: UInt64, cronExpression: String, handlerUUID: UInt64, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) /// Emitted when scheduling is rejected (due to duplicate/unauthorized scheduling) access(all) event CronScheduleRejected( txID: UInt64, cronExpression: String, handlerUUID: UInt64, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) /// Emitted when scheduling fails due to insufficient funds access(all) event CronScheduleFailed( txID: UInt64, executionMode: UInt8, requiredAmount: UFix64, availableAmount: UFix64, cronExpression: String, handlerUUID: UInt64, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) /// Emitted when fee estimation fails access(all) event CronEstimationFailed( txID: UInt64, executionMode: UInt8, priority: UInt8, executionEffort: UInt64, error: String?, cronExpression: String, handlerUUID: UInt64, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) /// Execution mode selector for dual-mode handler access(all) enum ExecutionMode: UInt8 { access(all) case Keeper access(all) case Executor } /// CronHandler resource wraps any TransactionHandler with fault-tolerant cron scheduling access(all) resource CronHandler: FlowTransactionScheduler.TransactionHandler, ViewResolver.Resolver { /// Cron expression for scheduling access(self) let cronExpression: String /// Cron spec for scheduling access(self) let cronSpec: FlowCronUtils.CronSpec /// The handler that performs the actual work access(self) let wrappedHandlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}> /// Vault capability for fee payments for rescheduling access(self) let feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault> /// Scheduler manager capability for rescheduling access(self) let schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}> /// Next scheduled keeper transaction ID /// - nil: Cron not running (bootstrap required) or restart case /// - non-nil: ID of the NEXT keeper transaction that will execute /// Used to prevent duplicate/unauthorized keeper scheduling access(self) var nextScheduledKeeperID: UInt64? /// Next scheduled executor transaction ID /// - nil: No executor scheduled yet /// - non-nil: ID of the NEXT executor transaction that will run user code /// Used for complete cancellation support access(self) var nextScheduledExecutorID: UInt64? init( cronExpression: String, wrappedHandlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>, feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>, schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}> ) { pre { cronExpression.length > 0: "Cron expression cannot be empty" wrappedHandlerCap.check(): "Invalid wrapped handler capability provided" feeProviderCap.check(): "Invalid fee provider capability" schedulerManagerCap.check(): "Invalid scheduler manager capability" } self.cronExpression = cronExpression self.cronSpec = FlowCronUtils.parse(expression: cronExpression) ?? panic("Invalid cron expression: ".concat(cronExpression)) self.wrappedHandlerCap = wrappedHandlerCap self.feeProviderCap = feeProviderCap self.schedulerManagerCap = schedulerManagerCap self.nextScheduledKeeperID = nil self.nextScheduledExecutorID = nil } /// Main execution entry point for scheduled transactions /// Routes to keeper or executor mode based on context access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) { // Parse execution context let context = data as? CronContext ?? panic("Invalid execution data: expected CronContext") // Route based on execution mode if context.executionMode == ExecutionMode.Keeper { // Keeper verification: Prevent duplicate/unauthorized keeper scheduling // This ensures only the keeper we scheduled can execute, blocking duplicate schedules while cron is running if let storedID = self.nextScheduledKeeperID { // We have a stored keeper ID, verify this execution matches it if let txData = FlowTransactionScheduler.getTransactionData(id: storedID) { // Data exists so transaction is scheduled, check this is the expected keeper if id != storedID { let wrappedHandler = self.wrappedHandlerCap.borrow() emit CronScheduleRejected( txID: id, cronExpression: self.cronExpression, handlerUUID: self.uuid, wrappedHandlerType: wrappedHandler?.getType()?.identifier, wrappedHandlerUUID: wrappedHandler?.uuid ) return } } } // No stored ID or verification passed so execute keeper mode self.executeKeeperMode(txID: id, context: context) } else { // Executor mode with no verification so they run independently without affecting the keeper chain self.executeExecutorMode(txID: id, context: context) } } /// Keeper mode: Pure scheduling logic, no user code execution /// Only calculates times and schedules transactions /// Schedules executor at cron tick and keeper with 1s offset (separate slots) access(self) fun executeKeeperMode(txID: UInt64, context: CronContext) { // Calculate next cron tick (for BOTH executor and keeper) let currentTime = UInt64(getCurrentBlock().timestamp) let nextTick = FlowCronUtils.nextTick(spec: self.cronSpec, afterUnix: currentTime) ?? panic("Cannot calculate next cron tick") // Schedule executor FIRST at exact cron tick // Returns nil if scheduling fails (only possible with High priority slot full) // No fallback to execute exactly as user meant it to run so that its work is explicit let executorTxID = self.scheduleCronTransaction( txID: txID, executionMode: ExecutionMode.Executor, timestamp: nextTick, priority: context.executorPriority, executionEffort: context.executorExecutionEffort, context: context ) // Store executor transaction ID for cancellation support (nil if scheduling failed) self.nextScheduledExecutorID = executorTxID // Determine keeper timestamp based on actual executor schedule // For Medium/Low priority, actual scheduled time may differ from requested nextTick // Keeper must run AFTER executor, so use actual executor timestamp + offset var actualExecutorTime: UInt64? = nil var keeperTimestamp = nextTick + FlowCron.keeperOffset if let execID = executorTxID { if let txData = FlowTransactionScheduler.getTransactionData(id: execID) { actualExecutorTime = UInt64(txData.scheduledTimestamp) keeperTimestamp = actualExecutorTime! + FlowCron.keeperOffset } } // Schedule keeper with offset from actual executor time (or nextTick if executor failed) let keeperTxID = self.scheduleCronTransaction( txID: txID, executionMode: ExecutionMode.Keeper, timestamp: keeperTimestamp, priority: FlowCron.keeperPriority, executionEffort: context.keeperExecutionEffort, context: context )! // Store keeper transaction ID to prevent duplicate scheduling self.nextScheduledKeeperID = keeperTxID // Emit keeper executed event with actual scheduled times let wrappedHandler = self.wrappedHandlerCap.borrow() emit CronKeeperExecuted( txID: txID, nextExecutorTxID: executorTxID, nextKeeperTxID: keeperTxID, nextExecutorTime: actualExecutorTime, nextKeeperTime: keeperTimestamp, cronExpression: self.cronExpression, handlerUUID: self.uuid, wrappedHandlerType: wrappedHandler?.getType()?.identifier, wrappedHandlerUUID: wrappedHandler?.uuid ) } /// Executor mode: Runs user's wrapped handler /// Executes arbitrary user code which may panic access(self) fun executeExecutorMode(txID: UInt64, context: CronContext) { // Execute wrapped handler // If this panics, transaction reverts but keeper was already scheduled in a keeper execution let wrappedHandler = self.wrappedHandlerCap.borrow() ?? panic("Cannot borrow wrapped handler capability") wrappedHandler.executeTransaction(id: txID, data: context.wrappedData) // Emit completion event emit CronExecutorExecuted( txID: txID, cronExpression: self.cronExpression, handlerUUID: self.uuid, wrappedHandlerType: wrappedHandler.getType().identifier, wrappedHandlerUUID: wrappedHandler.uuid ) } /// Unified scheduling function with explicit parameters and error handling /// Schedules a cron transaction (keeper or executor) with specified priority access(self) fun scheduleCronTransaction( txID: UInt64, executionMode: ExecutionMode, timestamp: UInt64, priority: FlowTransactionScheduler.Priority, executionEffort: UInt64, context: CronContext ): UInt64? { // Borrow capabilities let schedulerManager = self.schedulerManagerCap.borrow() ?? panic("Cannot borrow scheduler manager") let feeVault = self.feeProviderCap.borrow() ?? panic("Cannot borrow fee provider") // Create execution context preserving original executor/keeper config let execContext = CronContext( executionMode: executionMode, executorPriority: context.executorPriority, executorExecutionEffort: context.executorExecutionEffort, keeperExecutionEffort: context.keeperExecutionEffort, wrappedData: context.wrappedData ) // Estimate fees let estimate = FlowTransactionScheduler.estimate( data: execContext, timestamp: UFix64(timestamp), priority: priority, executionEffort: executionEffort ) // Handle estimation result let wrappedHandler = self.wrappedHandlerCap.borrow() if let requiredFee = estimate.flowFee { // Check sufficient balance if feeVault.balance >= requiredFee { // Schedule transaction let fees <- feeVault.withdraw(amount: requiredFee) let txID = schedulerManager.scheduleByHandler( handlerTypeIdentifier: self.getType().identifier, handlerUUID: self.uuid, data: execContext, timestamp: UFix64(timestamp), priority: priority, executionEffort: executionEffort, fees: <-fees as! @FlowToken.Vault ) return txID } else { // Insufficient funds, emits event emit CronScheduleFailed( txID: txID, executionMode: executionMode.rawValue, requiredAmount: requiredFee, availableAmount: feeVault.balance, cronExpression: self.cronExpression, handlerUUID: self.uuid, wrappedHandlerType: wrappedHandler?.getType()?.identifier, wrappedHandlerUUID: wrappedHandler?.uuid ) return nil } } // If we arrive here, estimation failed so emit event and return nil emit CronEstimationFailed( txID: txID, executionMode: executionMode.rawValue, priority: priority.rawValue, executionEffort: executionEffort, error: estimate.error, cronExpression: self.cronExpression, handlerUUID: self.uuid, wrappedHandlerType: wrappedHandler?.getType()?.identifier, wrappedHandlerUUID: wrappedHandler?.uuid ) return nil } /// Returns the cron expression access(all) view fun getCronExpression(): String { return self.cronExpression } /// Returns a copy of the cron spec for use in calculations access(all) view fun getCronSpec(): FlowCronUtils.CronSpec { return self.cronSpec } /// Returns the next scheduled keeper transaction ID if one exists access(all) view fun getNextScheduledKeeperID(): UInt64? { return self.nextScheduledKeeperID } /// Returns the next scheduled executor transaction ID if one exists access(all) view fun getNextScheduledExecutorID(): UInt64? { return self.nextScheduledExecutorID } access(all) view fun getViews(): [Type] { var views: [Type] = [ Type<MetadataViews.Display>(), Type<CronInfo>() ] // Add wrapped handler views, but deduplicate to avoid collisions if let handler = self.wrappedHandlerCap.borrow() { for viewType in handler.getViews() { if !views.contains(viewType) { views = views.concat([viewType]) } } } return views } access(all) fun resolveView(_ view: Type): AnyStruct? { let wrappedHandler = self.wrappedHandlerCap.borrow() switch view { case Type<MetadataViews.Display>(): // Try to get wrapped handler's display let wrappedDisplay = wrappedHandler?.resolveView(Type<MetadataViews.Display>()) as? MetadataViews.Display if let wrapped = wrappedDisplay { // Merge: Enrich wrapped handler's display with cron info return MetadataViews.Display( name: wrapped.name.concat(" (Cron)"), description: wrapped.description .concat(" (Cron: ").concat(self.cronExpression).concat(")"), thumbnail: wrapped.thumbnail ) } else { // Fallback: Cron-only display (when wrapped handler doesn't provide display) let handlerType = wrappedHandler?.getType()?.identifier ?? "Unknown" return MetadataViews.Display( name: "Cron Handler", description: "Scheduled handler: ".concat(handlerType) .concat(" (Cron: ").concat(self.cronExpression).concat(")"), thumbnail: MetadataViews.HTTPFile(url: "") ) } case Type<CronInfo>(): return CronInfo( cronExpression: self.cronExpression, cronSpec: self.cronSpec, nextScheduledKeeperID: self.nextScheduledKeeperID, nextScheduledExecutorID: self.nextScheduledExecutorID, wrappedHandlerType: wrappedHandler?.getType()?.identifier, wrappedHandlerUUID: wrappedHandler?.uuid ) default: return wrappedHandler?.resolveView(view) } } } /// Context passed to each cron execution access(all) struct CronContext { access(contract) let executionMode: ExecutionMode access(contract) let executorPriority: FlowTransactionScheduler.Priority access(contract) let executorExecutionEffort: UInt64 access(contract) let keeperExecutionEffort: UInt64 access(contract) let wrappedData: AnyStruct? init( executionMode: ExecutionMode, executorPriority: FlowTransactionScheduler.Priority, executorExecutionEffort: UInt64, keeperExecutionEffort: UInt64, wrappedData: AnyStruct? ) { pre { executorExecutionEffort >= 100: "Executor execution effort must be at least 100 (scheduler minimum)" executorExecutionEffort <= 9999: "Executor execution effort must be at most 9999 (scheduler maximum)" keeperExecutionEffort >= 100: "Keeper execution effort must be at least 100 (scheduler minimum)" keeperExecutionEffort <= 9999: "Keeper execution effort must be at most 9999 (scheduler maximum)" } self.executionMode = executionMode self.executorPriority = executorPriority self.executorExecutionEffort = executorExecutionEffort self.keeperExecutionEffort = keeperExecutionEffort self.wrappedData = wrappedData } } /// View structure exposing cron handler metadata access(all) struct CronInfo { /// The original cron expression string access(all) let cronExpression: String /// Parsed cron specification for execution access(all) let cronSpec: FlowCronUtils.CronSpec /// Next scheduled keeper transaction ID access(all) let nextScheduledKeeperID: UInt64? /// Next scheduled executor transaction ID access(all) let nextScheduledExecutorID: UInt64? /// Type identifier of wrapped handler access(all) let wrappedHandlerType: String? /// UUID of wrapped handler resource access(all) let wrappedHandlerUUID: UInt64? init( cronExpression: String, cronSpec: FlowCronUtils.CronSpec, nextScheduledKeeperID: UInt64?, nextScheduledExecutorID: UInt64?, wrappedHandlerType: String?, wrappedHandlerUUID: UInt64? ) { self.cronExpression = cronExpression self.cronSpec = cronSpec self.nextScheduledKeeperID = nextScheduledKeeperID self.nextScheduledExecutorID = nextScheduledExecutorID self.wrappedHandlerType = wrappedHandlerType self.wrappedHandlerUUID = wrappedHandlerUUID } } /// Create a new CronHandler resource access(all) fun createCronHandler( cronExpression: String, wrappedHandlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>, feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>, schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}> ): @CronHandler { return <- create CronHandler( cronExpression: cronExpression, wrappedHandlerCap: wrappedHandlerCap, feeProviderCap: feeProviderCap, schedulerManagerCap: schedulerManagerCap ) } init() { // Set fixed medium priority for keeper operations to balance reliability with cost efficiency self.keeperPriority = FlowTransactionScheduler.Priority.Medium // Keeper offset of 1 second to prevent race condition self.keeperOffset = 1 } }

Cadence Script

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