Skip to main content

Flow Scheduled Transactions Documentation

Introduction

warning

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.

info

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:


_12
access(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:


_43
access(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:

  1. Scheduled: Transaction is created and queued for future execution

    • Event: FlowTransactionScheduler.Scheduled
    • Status: Scheduled
  2. 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)
  3. Executed: Transaction has been processed by the blockchain

    • Event: FlowTransactionScheduler.Executed
    • Status: Executed
  4. Canceled: Transaction was canceled before execution (optional path)

    • Event: FlowTransactionScheduler.Canceled
    • Status: Canceled

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
_55
import "FlowTransactionScheduler"
_55
_55
access(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
_70
import "FlowTransactionScheduler"
_70
import "FlowTransactionSchedulerUtils"
_70
import "TestFlowScheduledTransactionHandler"
_70
import "FlowToken"
_70
import "FungibleToken"
_70
_70
transaction(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
_19
import "FlowTransactionScheduler"
_19
import "FlowToken"
_19
_19
transaction(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
_20
import "FlowTransactionScheduler"
_20
_20
access(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):


_10
flow 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