Flow Scheduled Transactions Documentation
Introduction
Scheduled transactions were part of the Forte network upgrade and are available on Flow Mainnet, Flow Emulator (CLI v2.7.0+) and Flow Testnet. For more infomation, see Forte: Introducing Actions & Agents.
Scheduled transactions on the Flow blockchain allow 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, which allows 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 help you manage your scheduled transactions more easily. Check out the scheduled transactions intro for a tutorial on how to schedule some basic transactions with the manager.
Concepts
Create a scheduled transaction
To create a scheduled transaction, the logic that executes in the transaction must already be defined in a function that the scheduler calls when it is time for the transaction to execute.
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, 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
In scheduling, you create the transaction that executes 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
TransactionHandlerinterface, like the FLOW transfer one above. - Timestamp: future Unix timestamp when execution should occur (fractional seconds ignored).
- Execution Effort: computational resources allocated (computation unit limit for the transaction).
- Fees: Flow tokens to cover execution costs and storage costs for the transaction data.
- Optional Data: arbitrary data that's possibly relevant to the transaction forwarded to the handler during execution.
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 with standard Flow fee structure.
- Priority multiplier: higher priorities pay more (High: 10x, Medium: 5x, Low: 2x base rate).
- Storage fee: cost to store transaction data on-chain.
Fees are paid upfront and are used in full. There are no refunds if the cost of execution was lower.
Please keep in mind the priority multiplier can change in the future. You can obtain the fee configuration from the contract, and you can use the estimate function 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.
Cancel transactions
You can cancel scheduled transactions before execution. When you cancel a transaction, it 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 scheduled transaction cancellation 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: The blockchain processed the transaction.
- 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 is 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. Schedule a transaction with the scripts manager
This example shows how to create and schedule a transaction that will execute at a future timestamp with 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. Query transaction information
Get Status: The get_status script demonstrates how to check the current status of a scheduled transaction with the global status function.
Get all Tx Info: The get_transaction_data 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.
4. Cancel 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 you actually submit it. This is useful for budget 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. Monitor execution events
Use the Flow Command Line Interface (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: create handlers, schedule execution, monitor events, and manage cancellations. The system provides flexibility for various automation scenarios while you maintain 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, which it easier to manage and monitor your scheduled transactions programmatically.
The flow-go-sdk will also add support for these new commands. It provides native integration for Go applications that work 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, visit github.com/onflow/flow and create an issue with the tag scheduled_transactions.
Read FLIP 330: Scheduled Callbacks for more details.