Flow Scheduled Transactions Documentation
Introduction
Scheduled transactions are part of the Forte network upgrade and are currently available on Flow Emulator (CLI v2.7.0+) and [Flow Testnet]. See the announcement for context: [Forte: Introducing Actions & Agents].
Scheduled transactions on the Flow blockchain enable users and 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.
Flow provides a scheduled transaction manager to make managing your scheduled transactions more streamlined. Check out the scheduled transactions intro for a tutorial on how to schedule some basic transactions with the manager.
Concepts
Creating a Scheduled Transaction
In order to create a scheduled transaction, the logic that will be executed in the transaction must already be defined in a function that the scheduler will call when it is time for the transaction to be executed.
Therefore, all scheduled transactions must include a capability to a resource that conforms to this Transaction Handler interface defined in the Scheduler contract and includes getters that conform to the Flow metadata views standard:
_12access(all) resource interface TransactionHandler {_12 // Called by the protocol to executed the scheduled transaction_12 // **Transaction ID**: Unique identifier for tracking, returned during scheduling_12 // **Data**: The optional data provided during scheduling that may relate_12 // to the specific scheduled transaction_12 access(Execute) fun executeTransaction(id: UInt64, data: AnyStruct?)_12_12 // Allows querying this handler to get metadata about it_12 // See the flow metadata views standard for more info_12 access(all) view fun getViews(): [Type]_12 access(all) fun resolveView(_ view: Type): AnyStruct?_12}
To schedule a transaction, you store an instance of this resource in your account storage and pass a capability to the scheduler contract as part of the schedule request.
Here is a simple example implementation for a Handler's executeTransaction()
function that transfers FLOW
at the scheduled time:
_43access(all) contract TransferFLOWHandler {_43_43 access(all) let HandlerStoragePath: StoragePath_43 access(all) let HandlerPublicPath: PublicPath_43 _43 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {_43_43 access(all) var from: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>_43_43 access(all) var amount: UFix64_43_43 // other functions left out for simplicity_43_43 // The actual logic that is executed when the scheduled transaction_43 // is executed_43 access(FlowTransactionScheduler.Execute) _43 fun executeTransaction(id: UInt64, data: AnyStruct?) {_43 if let to = data as Address {_43 let providerRef = self.from.borrow()_43 ?? panic("Could not borrow a reference to the provider FlowToken Vault")_43_43 // Get a reference to the recipient's Receiver_43 let receiverRef = getAccount(to)_43 .capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)_43 ?? panic("Could not borrow a Receiver reference to the FlowToken Vault in account \(to.toString())")_43_43 // Deposit the withdrawn tokens in the recipient's receiver_43 receiverRef.deposit(from: <-providerRef.withdraw(amount: self.amount))_43 _43 } else {_43 panic("Unable to transfer FLOW because the data provided when scheduling the transaction is not a Flow address!")_43 }_43 }_43 }_43_43 // A user would call this to get an instance of this handler_43 // for their own scheduling use_43 access(all) fun createHandler(amount: UFix64, from: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>): @Handler {_43 return <- create Handler(name: "Transfer FLOW Handler Resource", amount: amount, from: from)_43 }_43_43 // other functions left out for simplicity_43}
Scheduling
Scheduling involves creating the specific 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 the highest fees
- Medium Priority: Best-effort execution as close as possible to the scheduled time known during scheduling
- Low Priority: Opportunistic execution when network capacity allows, lowest fees but no guarantee about timing.
Each transaction requires:
- Handler Capability: A capability to a resource implementing
TransactionHandler
interface, like the FLOW transfer one above. - 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 and storage costs for the transaction data.
- Optional Data: Arbitrary data forwarded to the handler during execution that may be relevant to the transaction.
These arguments are required by the FlowTransactionScheduler.schedule()
function.
This function returns a ScheduledTransaction
resource object.
The Scheduled Transaction Manager standard (mentioned in the intro) provides an easy way for developers
and users to manage their scheduled transactions from a central place in their account. Users are strongly encouraged to use this.
More information about the Scheduled Transaction manager is in the section at the end of this document.
When a transaction is scheduled, the FlowTransactionScheduler.Scheduled
event
is emitted with information about the scheduled transaction and handler.
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.
Execution of Transaction Handlers
When the scheduled time arrives, the Flow blockchain calls the executeTransaction
method on your handler resource.
If the transaction succeeds, the FlowTransactionScheduler.Executed
event
is emitted with information about the executed transaction.
If the scheduled transaction fails at any point during execution, the Executed
event
is not emitted.
Canceling
Scheduled transactions can be canceled before execution. Canceling returns a portion of the fees (configurable refund percentage, 50% as of now). Please keep in mind the refund percentage can change in the future.
To cancel, you need the ScheduledTransaction
resource that was returned during scheduling. The scheduled transaction manager also makes cancelling scheduled transaction easier.
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:
Executed
(Executed does not necessarily mean it succeeded, just that execution was attempted)
- 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:
Contracts
The FlowTransactionScheduler
contract is deployed to the service account and manages all scheduled transactions across the network.
The FlowTransactionSchedulerUtils
contract provides utilities for scheduled transactions, such as the transaction Manager
resource, common handlers, and metadata views related to scheduled transactions.
Below are listed the addresses of both transaction scheduler contracts on each network they are deployed:
- Emulator:
0xf8d6e0586b0a20c7
- **Cadence Testing Framework:
0x0000000000000001
- Testnet:
0x8c5303eaa26202d6
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.
_55// TestFlowCallbackHandler.cdc - Simple test handler_55import "FlowTransactionScheduler"_55_55access(all) contract TestFlowScheduledTransactionHandler {_55 access(all) let HandlerStoragePath: StoragePath_55 access(all) let HandlerPublicPath: PublicPath_55 _55 access(all) event TransactionExecuted(data: String)_55_55 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {_55 _55 access(FlowTransactionScheduler.Execute) _55 fun executeTransaction(id: UInt64, data: AnyStruct?) {_55 if let string: String = data as? String {_55 emit TransactionExecuted(data: string)_55 } else {_55 emit TransactionExecuted(data: "bloop")_55 }_55 }_55_55 // public functions that anyone can call to get information about _55 // this handler_55 access(all) view fun getViews(): [Type] {_55 return [Type<StoragePath>(), Type<PublicPath>(), Type<MetadataViews.Display>()]_55 }_55_55 access(all) fun resolveView(_ view: Type): AnyStruct? {_55 switch view {_55 case Type<StoragePath>():_55 return TestFlowScheduledTransactionHandler.HandlerStoragePath_55 case Type<PublicPath>():_55 return TestFlowScheduledTransactionHandler.HandlerPublicPath_55 case Type<MetadataViews.Display>():_55 return MetadataViews.Display(_55 name: "Basic Scheduled Transaction Handler",_55 description: "Emits a TransactionExecuted event when the scheduled transaction is executed",_55 thumbnail: MetadataViews.HTTPFile(_55 url: ""_55 )_55 )_55 default:_55 return nil_55 }_55 }_55 }_55_55 access(all) fun createHandler(): @Handler {_55 return <- create Handler()_55 }_55_55 init() {_55 self.HandlerStoragePath = /storage/testCallbackHandler_55 self.HandlerPublicPath = /public/testCallbackHandler_55 }_55}
2. Scheduling a Transaction with the Manager
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 "FlowTransactionSchedulerUtils"_70import "TestFlowScheduledTransactionHandler"_70import "FlowToken"_70import "FungibleToken"_70_70transaction(timestamp: UFix64, feeAmount: UFix64, effort: UInt64, priority: UInt8, testData: AnyStruct?) {_70_70 prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) {_70_70 // if a transaction scheduler manager has not been created for this account yet, create one_70 if !account.storage.check<@{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath) {_70 let manager <- FlowTransactionSchedulerUtils.createManager()_70 account.storage.save(<-manager, to: FlowTransactionSchedulerUtils.managerStoragePath)_70_70 // create a public capability to the callback manager_70 let managerRef = account.capabilities.storage.issue<&{FlowTransactionSchedulerUtils.Manager}>(FlowTransactionSchedulerUtils.managerStoragePath)_70 account.capabilities.publish(managerRef, at: FlowTransactionSchedulerUtils.managerPublicPath)_70 }_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 transaction_70 if !account.storage.check<@TestFlowScheduledTransactionHandler.Handler>(from: TestFlowScheduledTransactionHandler.HandlerStoragePath) {_70 let handler <- TestFlowScheduledTransactionHandler.createHandler()_70 _70 account.storage.save(<-handler, to: TestFlowScheduledTransactionHandler.HandlerStoragePath)_70 account.capabilities.storage.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(TestFlowScheduledTransactionHandler.HandlerStoragePath)_70 _70 let publicHandlerCap = account.capabilities.storage.issue<&{FlowTransactionScheduler.TransactionHandler}>(TestFlowScheduledTransactionHandler.HandlerStoragePath)_70 account.capabilities.publish(publicHandlerCap, at: TestFlowScheduledTransactionHandler.HandlerPublicPath)_70 }_70_70 // Get the entitled capability that will be used to create the transaction_70 // Need to check both controllers because the order of controllers is not guaranteed_70 var handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>? = nil_70 _70 if let cap = account.capabilities.storage_70 .getControllers(forPath: TestFlowScheduledTransactionHandler.HandlerStoragePath)[0]_70 .capability as? Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}> {_70 handlerCap = cap_70 } else {_70 handlerCap = account.capabilities.storage_70 .getControllers(forPath: TestFlowScheduledTransactionHandler.HandlerStoragePath)[1]_70 .capability as! Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>_70 }_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 fees <- vault.withdraw(amount: feeAmount) as! @FlowToken.Vault_70 let priorityEnum = FlowTransactionScheduler.Priority(rawValue: priority)_70 ?? FlowTransactionScheduler.Priority.High_70_70 // borrow a reference to the callback manager_70 let manager = account.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath)_70 ?? panic("Could not borrow a Manager reference from \(FlowTransactionSchedulerUtils.managerStoragePath)")_70_70 // Schedule the regular transaction with the main contract_70 manager.schedule(_70 handlerCap: handlerCap!,_70 data: testData,_70 timestamp: timestamp,_70 priority: priorityEnum,_70 executionEffort: effort,_70 fees: <-fees_70 )_70 }_70}
3. Querying Transaction Information
Get Status: This script demonstrates how to check the current status of a scheduled transaction using the global status function.
Get all Tx Info: This script gets all the internal information about a scheduled transaction.
Manager Scripts
The manager provides many different ways to get information about all of your scheduled transactions. Check out all the scripts you can use with your manager here.
4. Canceling a Scheduled Transaction
This transaction shows how to cancel a scheduled transaction and receive a partial refund of the fees paid.
_19// cancel_transaction.cdc_19import "FlowTransactionScheduler"_19import "FlowToken"_19_19transaction(transactionId: UInt64) {_19 prepare(account: auth(BorrowValue, SaveValue, LoadValue) &Account) {_19 _19 // borrow a reference to the manager_19 let manager = account.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath)_19 ?? panic("Could not borrow a Manager reference from \(FlowTransactionSchedulerUtils.managerStoragePath)")_19_19 // Get the vault where the refund should be deposited_19 let vault = account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)_19 ?? panic("Could not borrow FlowToken vault")_19_19 // cancel the transaction_19 vault.deposit(from: <-manager.cancel(id: id))_19 }_19}
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.TransactionExecuted \_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
- TransactionExecuted: 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