Skip to main content

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:


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

  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: Scheduled (still scheduled until actually executed)
  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

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
_30
import "FlowTransactionScheduler"
_30
_30
access(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
_70
import "FlowTransactionScheduler"
_70
import "TestFlowCallbackHandler"
_70
import "FlowToken"
_70
import "FungibleToken"
_70
_70
transaction(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
_10
import "FlowTransactionScheduler"
_10
_10
access(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
_21
import "FlowTransactionScheduler"
_21
import "FlowToken"
_21
_21
transaction(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
_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.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