Flow Scheduled Transactions Documentation
Introduction
Scheduled transactions on the Flow blockchain enable smart contracts to autonomously execute predefined logic at specific future times without external triggers. This powerful feature allows developers to create "wake up" patterns where contracts can schedule themselves to run at predetermined block timestamps, enabling novel blockchain automation patterns.
Key benefits include:
- Autonomous execution: No need for external services or manual intervention
- Time-based automation: Execute transactions based on blockchain time
- Predictable scheduling: Guaranteed execution within specified time windows
Common use cases include recurring payments, automated arbitrage, time-based contract logic, delayed executions, and periodic maintenance tasks.
Concepts
Scheduling
Scheduling involves creating a transaction that will execute at a specified future timestamp. The system uses three priority levels:
- High Priority: Guarantees execution in the first block with the scheduled time or fails scheduling, requires with highest fees
- Medium Priority: Best-effort execution closest to the scheduled time known during scheduling
- Low Priority: Opportunistic execution when network capacity allows, lowest fees but no time returned during scheduling
Each transaction requires:
- Handler Capability: A capability to a resource implementing
TransactionHandler
interface - Timestamp: Future Unix timestamp when execution should occur (fractional seconds ignored)
- Execution Effort: Computational resources allocated (gas limit for the transaction)
- Fees: Flow tokens to cover execution costs
- Optional Data: Arbitrary data forwarded to the handler during execution
Execution of Transaction Handlers
When the scheduled time arrives, the Flow blockchain calls the executeTransaction
method on your handler resource. The handler receives:
- Transaction ID: Unique identifier for tracking, returned during scheduling
- Data: The optional data provided during scheduling
Your handler must implement the TransactionHandler
interface:
_10access(all) resource interface TransactionHandler {_10 access(Execute) fun executeTransaction(id: UInt64, data: AnyStruct?)_10}
Canceling
Scheduled transactions can be canceled before execution if they're still in Scheduled
status. Canceling returns a portion of the fees (configurable refund percentage, 50% as of now). To cancel, you need the ScheduledTransaction
resource returned during scheduling. Please keep in mind the refund percentage can change in the future.
Fees
Fee calculation includes:
- Base execution fee: Based on computational effort using standard Flow fee structure
- Priority multiplier: Higher priorities pay more (High: 10x, Medium: 5x, Low: 2x base rate)
- Storage fee: Cost for storing transaction data on-chain
Fees are paid upfront and are used in full, no refunds if the cost of execution was lower.
Please keep in mind the priority multiplier can change in the future. The fee configuration can be obtained from the contract, and estimate function can be used to check the fees upfront.
Transaction Lifecycle
Scheduled transactions follow a specific lifecycle with corresponding events:
-
Scheduled: Transaction is created and queued for future execution
- Event:
FlowTransactionScheduler.Scheduled
- Status:
Scheduled
- Event:
-
Pending Execution: Transaction timestamp has arrived and it's ready for execution
- Event:
FlowTransactionScheduler.PendingExecution
- Status:
Scheduled
(still scheduled until actually executed)
- Event:
-
Executed: Transaction has been processed by the blockchain
- Event:
FlowTransactionScheduler.Executed
- Status:
Executed
- Event:
-
Canceled: Transaction was canceled before execution (optional path)
- Event:
FlowTransactionScheduler.Canceled
- Status:
Canceled
- Event:
The FlowTransactionScheduler contract is deployed to the service account and manages all scheduled transactions across the network.
Examples
1. Example Test Handler Contract
This contract implements the TransactionHandler interface and will be used in the following examples. It emits events when scheduled transactions are executed.
_30// TestFlowCallbackHandler.cdc - Simple test handler_30import "FlowTransactionScheduler"_30_30access(all) contract TestFlowCallbackHandler {_30 access(all) let HandlerStoragePath: StoragePath_30 access(all) let HandlerPublicPath: PublicPath_30 _30 access(all) event CallbackExecuted(data: String)_30_30 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {_30 _30 access(FlowTransactionScheduler.Execute) _30 fun executeTransaction(id: UInt64, data: AnyStruct?) {_30 if let string: String = data as? String {_30 emit CallbackExecuted(data: string)_30 } else {_30 emit CallbackExecuted(data: "bloop")_30 }_30 }_30 }_30_30 access(all) fun createHandler(): @Handler {_30 return <- create Handler()_30 }_30_30 init() {_30 self.HandlerStoragePath = /storage/testCallbackHandler_30 self.HandlerPublicPath = /public/testCallbackHandler_30 }_30}
2. Scheduling a Transaction
This example shows how to create and schedule a transaction that will execute at a future timestamp using the TestFlowCallbackHandler from Example 1.
_70// schedule.cdc_70import "FlowTransactionScheduler"_70import "TestFlowCallbackHandler" _70import "FlowToken"_70import "FungibleToken"_70_70transaction(timestamp: UFix64, feeAmount: UFix64, effort: UInt64, priority: UInt8, testData: String) {_70 // Note: timestamp should be a future Unix timestamp, e.g., 1858010617.0 _70 prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) {_70 _70 // If a transaction handler has not been created for this account yet, create one,_70 // store it, and issue a capability that will be used to create the scheduled transaction_70 if !account.storage.check<@TestFlowCallbackHandler.Handler>(from: TestFlowCallbackHandler.HandlerStoragePath) {_70 let handler <- TestFlowCallbackHandler.createHandler()_70 _70 account.storage.save(<-handler, to: TestFlowCallbackHandler.HandlerStoragePath)_70 account.capabilities.storage.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(TestFlowCallbackHandler.HandlerStoragePath)_70 }_70_70 // Get the capability that will be used to create the scheduled transaction_70 let handlerCap = account.capabilities.storage_70 .getControllers(forPath: TestFlowCallbackHandler.HandlerStoragePath)[0]_70 .capability as! Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>_70 _70 // Borrow a reference to the vault that will be used for fees_70 let vault = account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)_70 ?? panic("Could not borrow FlowToken vault")_70 _70 let priorityEnum = FlowTransactionScheduler.Priority(rawValue: priority)_70 ?? FlowTransactionScheduler.Priority.High_70_70 // Estimate the fee first to ensure we have enough funds_70 let estimate = FlowTransactionScheduler.estimate(_70 data: testData,_70 timestamp: timestamp,_70 priority: priorityEnum,_70 executionEffort: effort_70 )_70 _70 // Check if estimation was successful and we have enough funds_70 if estimate.error != nil {_70 panic("Cannot schedule transaction: ".concat(estimate.error!))_70 }_70 _70 let requiredFee = estimate.flowFee!_70 if vault.balance < requiredFee {_70 panic("Insufficient funds: need ".concat(requiredFee.toString()).concat(" FLOW but only have ").concat(vault.balance.toString()))_70 }_70 _70 let fees <- vault.withdraw(amount: requiredFee) as! @FlowToken.Vault_70_70 // Schedule the transaction with the main contract_70 let scheduledTransaction <- FlowTransactionScheduler.schedule(_70 handlerCap: handlerCap,_70 data: testData,_70 timestamp: timestamp,_70 priority: priorityEnum,_70 executionEffort: effort,_70 fees: <-fees_70 )_70 _70 // Check the status_70 let status = scheduledTransaction.status()_70_70 // Store the scheduled transaction resource so we can query its status and cancel it if needed_70 let txID = scheduledTransaction.id_70 let storagePath = StoragePath(identifier: "scheduledTx_".concat(txID.toString()))!_70 account.storage.save(<-scheduledTransaction, to: storagePath)_70 }_70}
3. Querying Transaction Status
This script demonstrates how to check the current status of a scheduled transaction using the global status function.
_10// query_status.cdc - Script to check the status of a scheduled transaction_10import "FlowTransactionScheduler"_10_10access(all) fun main(transactionId: UInt64): FlowTransactionScheduler.Status? {_10 return FlowTransactionScheduler.getStatus(id: transactionId)_10}
4. Canceling a Scheduled Transaction
This transaction shows how to cancel a scheduled transaction and receive a partial refund of the fees paid.
_21// cancel_transaction.cdc_21import "FlowTransactionScheduler"_21import "FlowToken"_21_21transaction(transactionId: UInt64) {_21 prepare(account: auth(BorrowValue, SaveValue, LoadValue) &Account) {_21 _21 // Load the scheduled transaction from storage_21 let scheduledTx <- account.storage.load<@FlowTransactionScheduler.ScheduledTransaction>(_21 from: StoragePath(identifier: "scheduledTx_".concat(transactionId.toString()))!_21 ) ?? panic("Could not load scheduled transaction with ID ".concat(transactionId.toString()))_21 _21 // Cancel and get refund_21 let refundedFees <- FlowTransactionScheduler.cancel(scheduledTx: <-scheduledTx)_21 _21 // Deposit refund back to account_21 let vault = account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)_21 ?? panic("Could not borrow FlowToken vault")_21 vault.deposit(from: <-refundedFees)_21 }_21}
5. Fee Estimation
This script helps estimate the cost of scheduling a transaction before actually submitting it, useful for budgeting and validation.
_20// estimate_fees.cdc - Script to estimate scheduling costs _20import "FlowTransactionScheduler"_20_20access(all) fun main(_20 dataSize: AnyStruct?,_20 timestamp: UFix64,_20 priority: UInt8,_20 executionEffort: UInt64_20): FlowTransactionScheduler.EstimatedScheduledTransaction {_20 _20 let priorityEnum = FlowTransactionScheduler.Priority(rawValue: priority)_20 ?? FlowTransactionScheduler.Priority.Medium_20 _20 return FlowTransactionScheduler.estimate(_20 data: dataSize,_20 timestamp: timestamp, _20 priority: priorityEnum,_20 executionEffort: executionEffort_20 )_20}
6. Monitoring Execution Events
Use the Flow CLI to monitor all scheduled transaction events in real-time (example for testnet - account addresses may differ):
_10flow events get \_10 A.8c5303eaa26202d6.FlowTransactionScheduler.Scheduled \_10 A.8c5303eaa26202d6.FlowTransactionScheduler.PendingExecution \_10 A.8c5303eaa26202d6.FlowTransactionScheduler.Executed \_10 A.8c5303eaa26202d6.FlowTransactionScheduler.Canceled \_10 A.373ce83aef691d2d.TestFlowCallbackHandler.CallbackExecuted \_10 --last 200 \_10 -n testnet
This command fetches the last 200 blocks of events for:
- Scheduled: When a transaction is scheduled
- PendingExecution: When a transaction is ready for execution
- Executed: When a transaction has been executed
- Canceled: When a transaction is canceled
- CallbackExecuted: Custom event from the test handler
These examples demonstrate the complete lifecycle of scheduled transactions: creating handlers, scheduling execution, monitoring events, and managing cancellations. The system provides flexibility for various automation scenarios while maintaining network stability through resource limits and priority management.
Tools
Support for scheduled transactions in different tools is still work in progress and is coming soon. The Flow CLI and Access Node API will support specific commands and APIs to query scheduled transactions by ID, making it easier to manage and monitor your scheduled transactions programmatically.
The flow-go-sdk will also add support for these new commands, providing native integration for Go applications working with scheduled transactions.
Block explorer support for scheduled transactions is also coming, which will provide a visual interface to view and track scheduled transaction execution on the Flow blockchain.
For feature requests and suggestions for scheduled transaction tooling, please visit github.com/onflow/flow and create an issue with the tag scheduled_transactions
.
Read FLIP for more details: https://github.com/onflow/flips/blob/main/protocol/20250609-scheduled-callbacks.md