From 79f8c21f315bc805b60db5dcafb2f83a7999c707 Mon Sep 17 00:00:00 2001 From: mfbz Date: Mon, 12 Jan 2026 22:12:49 +0100 Subject: [PATCH 1/9] Added FlowCron docs --- .../cadence/advanced-concepts/flow-cron.md | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 docs/build/cadence/advanced-concepts/flow-cron.md diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md new file mode 100644 index 0000000000..9e8185fed3 --- /dev/null +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -0,0 +1,330 @@ +--- +title: Cron-based Recurring Transactions +sidebar_position: 9 +description: Learn how to schedule recurring transactions on Flow using FlowCron smart contract. +--- + +# Cron-based Recurring Transactions + +On traditional systems, cron jobs handle recurring executions. On Flow, the **FlowCron** smart contract brings the same familiar cron syntax onchain. You define a schedule like `0 0 * * *` (daily at midnight) using a cron expression, and your transaction runs countinously based on the schedule. + +:::info + +FlowCron builds on Flow's Scheduled Transactions. If you haven't worked with scheduled transactions before, check out the [Scheduled Transactions documentation](scheduled-transactions.md) first. + +::: + +## How It Works + +FlowCron provides a **CronHandler** resource that wraps any existing [TransactionHandler]. You give it a cron expression and your handler, and it takes care of scheduling and perpetuating the execution cycle. Once started, the schedule runs indefinitely without further intervention. + +Under the hood, the CronHandler runs two types of transactions per tick to ensure fault tolerance: + +- **Executor**: Runs your code. If your logic fails, only this transaction reverts. +- **Keeper**: Schedules the next cycle. Runs independently so the schedule survives even if your code throws an error. + +This separation keeps the recurring loop alive regardless of what happens in your handler. + +``` +Timeline ─────────────────────────────────────────────────────────> + T1 T2 T3 + │ │ │ + ├── Executor ──────────►├── Executor ──────────►├── Executor + │ (runs user code) │ (runs user code) │ (runs user code) + │ │ │ + └── Keeper ────────────►└── Keeper ────────────►└── Keeper + (schedules T2) (schedules T3) (schedules T4) + (+1s offset) (+1s offset) (+1s offset) +``` + +## Cron Expressions + +FlowCron uses the standard 5-field cron format that you may already know from Unix systems: + +``` +┌───────────── minute (0-59) +│ ┌───────────── hour (0-23) +│ │ ┌───────────── day of month (1-31) +│ │ │ ┌───────────── month (1-12) +│ │ │ │ ┌───────────── day of week (0-6, Sunday=0) +│ │ │ │ │ +* * * * * +``` + +**Operators:** `*` (any), `,` (list), `-` (range), `/` (step) + +| Pattern | When it runs | +| --- | --- | +| `* * * * *` | Every minute | +| `*/5 * * * *` | Every 5 minutes | +| `0 * * * *` | Top of every hour | +| `0 0 * * *` | Daily at midnight | +| `0 0 * * 0` | Weekly on Sunday | +| `0 9-17 * * 1-5` | Hourly, 9am-5pm weekdays | + +:::note + +When you specify both day-of-month and day-of-week (not `*`), the job runs if **either** matches. So `0 0 15 * 0` fires on the 15th OR on Sundays. + +::: + +## Setup + +Setting up a cron job involves creating a handler for your logic, wrapping it with FlowCron and scheduling the first tick. All transactions and scripts referenced below are available in the [FlowCron GitHub repository]. + +Before you start, make sure you have: + +- Flow CLI installed +- Some FLOW for transaction fees +- A [TransactionHandler] containing your recurring logic + +### 1. Create Your Handler + +Create a contract that implements the `TransactionHandler` interface: + +```cadence +import "FlowTransactionScheduler" + +access(all) contract MyRecurringTask { + + access(all) resource Handler: FlowTransactionScheduler.TransactionHandler { + + access(FlowTransactionScheduler.Execute) + fun executeTransaction(id: UInt64, data: AnyStruct?) { + // Your logic here + log("Cron fired at ".concat(getCurrentBlock().timestamp.toString())) + } + + access(all) view fun getViews(): [Type] { + return [Type(), Type()] + } + + access(all) fun resolveView(_ view: Type): AnyStruct? { + switch view { + case Type(): + return /storage/MyRecurringTaskHandler + case Type(): + return /public/MyRecurringTaskHandler + default: + return nil + } + } + } + + access(all) fun createHandler(): @Handler { + return <- create Handler() + } +} +``` + +Deploy this and save a handler instance to storage. See [CounterTransactionHandler.cdc] for a working example. + +### 2. Wrap It with FlowCron + +Create a CronHandler that wraps your handler with a cron expression: + +```cadence +transaction( + cronExpression: String, + wrappedHandlerStoragePath: StoragePath, + cronHandlerStoragePath: StoragePath +) { + prepare(acct: auth(BorrowValue, IssueStorageCapabilityController, SaveValue) &Account) { + // Issue capability for wrapped handler + let wrappedHandlerCap = acct.capabilities.storage.issue< + auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler} + >(wrappedHandlerStoragePath) + + // Create and save the CronHandler + let cronHandler <- FlowCron.createCronHandler( + cronExpression: cronExpression, + wrappedHandlerCap: wrappedHandlerCap, + feeProviderCap: feeProviderCap, + schedulerManagerCap: schedulerManagerCap + ) + acct.storage.save(<-cronHandler, to: cronHandlerStoragePath) + } +} +``` + +See [CreateCronHandler.cdc] for the full transaction. Run it with: + +```bash +flow transactions send CreateCronHandler.cdc \ + "*/5 * * * *" \ + /storage/MyRecurringTaskHandler \ + /storage/MyCronHandler +``` + +### 3. Start the Schedule + +Schedule the first executor and keeper to kick off the perpetual loop: + +```cadence +transaction( + cronHandlerStoragePath: StoragePath, + wrappedData: AnyStruct?, + executorPriority: UInt8, + executorExecutionEffort: UInt64, + keeperExecutionEffort: UInt64 +) { + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, SaveValue) &Account) { + // Calculate next cron tick time + let cronHandler = signer.storage.borrow<&FlowCron.CronHandler>(from: cronHandlerStoragePath) + ?? panic("CronHandler not found") + let executorTime = FlowCronUtils.nextTick(spec: cronHandler.getCronSpec(), afterUnix: currentTime) + } + + execute { + // Schedule executor (runs your code) + self.manager.schedule( + handlerCap: self.cronHandlerCap, + data: self.executorContext, + timestamp: UFix64(self.executorTime), + ... + ) + // Schedule keeper (schedules next cycle) + self.manager.schedule( + handlerCap: self.cronHandlerCap, + data: self.keeperContext, + timestamp: UFix64(self.keeperTime), + ... + ) + } +} +``` + +See [ScheduleCronHandler.cdc] for the full transaction. Run it with: + +```bash +flow transactions send ScheduleCronHandler.cdc \ + /storage/MyCronHandler \ + nil \ + 2 \ + 500 \ + 2500 +``` + +**Parameters:** + +| Parameter | Description | +| --- | --- | +| `cronHandlerStoragePath` | Path to your CronHandler | +| `wrappedData` | Optional data passed to handler (`nil` or your data) | +| `executorPriority` | 0 (High), 1 (Medium), or 2 (Low) | +| `executorExecutionEffort` | Computation units for your code (start with `500`) | +| `keeperExecutionEffort` | Computation units for keeper (use `2500`) | + +### 4. Check Status + +Query your cron job's metadata with [GetCronInfo.cdc]: + +```cadence +access(all) fun main(handlerAddress: Address, handlerStoragePath: StoragePath): FlowCron.CronInfo? { + let account = getAuthAccount(handlerAddress) + if let handler = account.storage.borrow<&FlowCron.CronHandler>(from: handlerStoragePath) { + return handler.resolveView(Type()) as? FlowCron.CronInfo + } + return nil +} +``` + +```bash +flow scripts execute GetCronInfo.cdc 0xYourAddress /storage/MyCronHandler +``` + +Calculate when the next tick will occur with [GetNextExecutionTime.cdc]: + +```cadence +access(all) fun main(cronExpression: String, afterUnix: UInt64?): UFix64? { + let cronSpec = FlowCronUtils.parse(expression: cronExpression) + if cronSpec == nil { return nil } + let nextTime = FlowCronUtils.nextTick( + spec: cronSpec!, + afterUnix: afterUnix ?? UInt64(getCurrentBlock().timestamp) + ) + return nextTime != nil ? UFix64(nextTime!) : nil +} +``` + +```bash +flow scripts execute GetNextExecutionTime.cdc "*/5 * * * *" nil +``` + +Additional scripts for debugging: + +- [GetCronScheduleStatus.cdc] — Returns executor/keeper IDs, timestamps, and status +- [GetParsedCronExpression.cdc] — Validates and parses a cron expression into a `CronSpec` + +## Stopping a Cron Job + +To stop a running cron job, cancel both the executor and keeper transactions: + +```cadence +transaction(cronHandlerStoragePath: StoragePath) { + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, SaveValue) &Account) { + let cronHandler = signer.storage.borrow<&FlowCron.CronHandler>(from: cronHandlerStoragePath) + ?? panic("CronHandler not found") + + self.executorID = cronHandler.getNextScheduledExecutorID() + self.keeperID = cronHandler.getNextScheduledKeeperID() + } + + execute { + // Cancel executor and keeper, receive fee refunds + if let id = self.executorID { + let refund <- self.manager.cancel(id: id) + self.feeReceiver.deposit(from: <-refund) + } + if let id = self.keeperID { + let refund <- self.manager.cancel(id: id) + self.feeReceiver.deposit(from: <-refund) + } + } +} +``` + +See [CancelCronSchedule.cdc] for the full transaction. Run it with: + +```bash +flow transactions send CancelCronSchedule.cdc /storage/MyCronHandler +``` + +Cancelling refunds 50% of the prepaid fees back to your account. + +## Contract Addresses + +FlowCron is deployed on both Testnet and Mainnet: + +| Contract | Testnet | Mainnet | +| --- | --- | --- | +| FlowCron | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | +| FlowCronUtils | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | + +## Resources + +- [FlowCron GitHub repository] +- [Scheduled Transactions Documentation] + + + +[FlowCron GitHub repository]: https://github.com/onflow/flow-cron +[Scheduled Transactions Documentation]: scheduled-transactions.md +[TransactionHandler]: scheduled-transactions.md#create-a-scheduled-transaction + + + +[CreateCronHandler.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/transactions/CreateCronHandler.cdc +[ScheduleCronHandler.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/transactions/ScheduleCronHandler.cdc +[CancelCronSchedule.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/transactions/CancelCronSchedule.cdc + + + +[GetCronInfo.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/scripts/GetCronInfo.cdc +[GetNextExecutionTime.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/scripts/GetNextExecutionTime.cdc +[GetCronScheduleStatus.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/scripts/GetCronScheduleStatus.cdc +[GetParsedCronExpression.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/scripts/GetParsedCronExpression.cdc + + + +[CounterTransactionHandler.cdc]: https://github.com/onflow/flow-cron/blob/main/cadence/tests/mocks/contracts/CounterTransactionHandler.cdc From 44e93ca145107d212eaac4423fb1c0a269579fe1 Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:45:19 +0100 Subject: [PATCH 2/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 9e8185fed3..76912cadb3 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -1,5 +1,5 @@ --- -title: Cron-based Recurring Transactions +title: Cron-Based Recurring Transactions sidebar_position: 9 description: Learn how to schedule recurring transactions on Flow using FlowCron smart contract. --- From 151f40b71956237113bcad5afdfbaa7f2794b95c Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:45:28 +0100 Subject: [PATCH 3/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 76912cadb3..22c3f58dd4 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -1,7 +1,7 @@ --- title: Cron-Based Recurring Transactions sidebar_position: 9 -description: Learn how to schedule recurring transactions on Flow using FlowCron smart contract. +description: Learn how to schedule recurring transactions on Flow using the FlowCron smart contract. --- # Cron-based Recurring Transactions From 910362d45cb0374ea47097272d87d2a95dc5257b Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:45:36 +0100 Subject: [PATCH 4/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 22c3f58dd4..938c0fe740 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -18,7 +18,7 @@ FlowCron builds on Flow's Scheduled Transactions. If you haven't worked with sch FlowCron provides a **CronHandler** resource that wraps any existing [TransactionHandler]. You give it a cron expression and your handler, and it takes care of scheduling and perpetuating the execution cycle. Once started, the schedule runs indefinitely without further intervention. -Under the hood, the CronHandler runs two types of transactions per tick to ensure fault tolerance: +Under the hood, the `CronHandler` runs two types of transactions per tick to ensure fault tolerance: - **Executor**: Runs your code. If your logic fails, only this transaction reverts. - **Keeper**: Schedules the next cycle. Runs independently so the schedule survives even if your code throws an error. From a1d41424c267f19c6f9d28fd7147b540843a3307 Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:45:43 +0100 Subject: [PATCH 5/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 938c0fe740..8f6efbf901 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -16,7 +16,7 @@ FlowCron builds on Flow's Scheduled Transactions. If you haven't worked with sch ## How It Works -FlowCron provides a **CronHandler** resource that wraps any existing [TransactionHandler]. You give it a cron expression and your handler, and it takes care of scheduling and perpetuating the execution cycle. Once started, the schedule runs indefinitely without further intervention. +FlowCron provides a `CronHandler` resource that wraps any existing [TransactionHandler]. You give it a cron expression and your handler, and it takes care of scheduling and perpetuating the execution cycle. Once started, the schedule runs indefinitely without further intervention. Under the hood, the `CronHandler` runs two types of transactions per tick to ensure fault tolerance: From ede83c97511f9ed28fce589259f34f2cf296bd02 Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:45:50 +0100 Subject: [PATCH 6/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 8f6efbf901..c71eff0776 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -70,7 +70,7 @@ When you specify both day-of-month and day-of-week (not `*`), the job runs if ** ## Setup -Setting up a cron job involves creating a handler for your logic, wrapping it with FlowCron and scheduling the first tick. All transactions and scripts referenced below are available in the [FlowCron GitHub repository]. +Setting up a cron job involves creating a handler for your logic, wrapping it with `FlowCron` and scheduling the first tick. All transactions and scripts referenced below are available in the [FlowCron GitHub repository]. Before you start, make sure you have: From 7e938bf0a89bb206b3dc78f9e23d56d0d390c311 Mon Sep 17 00:00:00 2001 From: Michael Fabozzi <39808567+mfbz@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:46:00 +0100 Subject: [PATCH 7/9] Update docs/build/cadence/advanced-concepts/flow-cron.md Co-authored-by: Chase Fleming --- docs/build/cadence/advanced-concepts/flow-cron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index c71eff0776..3c1e376f26 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -119,7 +119,7 @@ access(all) contract MyRecurringTask { Deploy this and save a handler instance to storage. See [CounterTransactionHandler.cdc] for a working example. -### 2. Wrap It with FlowCron +### 2. Wrap It with `FlowCron` Create a CronHandler that wraps your handler with a cron expression: From 3f13364f6f5310dc2621510ed9ca6d55229e2759 Mon Sep 17 00:00:00 2001 From: mfbz Date: Tue, 13 Jan 2026 17:41:31 +0100 Subject: [PATCH 8/9] Improved docs --- .../cadence/advanced-concepts/flow-cron.md | 141 ++++++++++++++---- 1 file changed, 113 insertions(+), 28 deletions(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 3c1e376f26..58686fc59c 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -4,9 +4,13 @@ sidebar_position: 9 description: Learn how to schedule recurring transactions on Flow using the FlowCron smart contract. --- -# Cron-based Recurring Transactions +# Cron-Based Recurring Transactions -On traditional systems, cron jobs handle recurring executions. On Flow, the **FlowCron** smart contract brings the same familiar cron syntax onchain. You define a schedule like `0 0 * * *` (daily at midnight) using a cron expression, and your transaction runs countinously based on the schedule. +Sometimes you need blockchain logic to run automatically on a schedule: distributing rewards every day, checking conditions every hour, or processing batches every week. Instead of manually triggering these transactions, you can automate them. + +**Cron** is a time-based scheduling system originally from Unix. It lets you define "run this at 9am every Monday" using a simple pattern called a cron expression. The **FlowCron** smart contract brings this same concept onchain, so you can schedule recurring transactions that run automatically without any external triggers. + +For example, with the cron expression `0 0 * * *` (daily at midnight), your transaction executes every day at midnight UTC—indefinitely—until you stop it. :::info @@ -16,14 +20,18 @@ FlowCron builds on Flow's Scheduled Transactions. If you haven't worked with sch ## How It Works -FlowCron provides a `CronHandler` resource that wraps any existing [TransactionHandler]. You give it a cron expression and your handler, and it takes care of scheduling and perpetuating the execution cycle. Once started, the schedule runs indefinitely without further intervention. +FlowCron provides a `CronHandler` resource that wraps your existing [TransactionHandler]. You give it a cron expression (like `*/5 * * * *` for every 5 minutes) and your handler, and FlowCron takes care of the rest. Once started, your schedule runs indefinitely without any further action from you. + +### Why Two Transactions? -Under the hood, the `CronHandler` runs two types of transactions per tick to ensure fault tolerance: +A key challenge with recurring schedules is fault tolerance: what happens if your code has a bug? You don't want one failed execution to break the entire schedule. + +FlowCron solves this by running two separate transactions each time your cron triggers: - **Executor**: Runs your code. If your logic fails, only this transaction reverts. -- **Keeper**: Schedules the next cycle. Runs independently so the schedule survives even if your code throws an error. +- **Keeper**: Schedules the next cycle. Runs independently, so even if your code throws an error, the schedule continues. -This separation keeps the recurring loop alive regardless of what happens in your handler. +**The benefit**: Your recurring schedule won't break if your TransactionHandler execution fails. The keeper always ensures the next execution is scheduled, regardless of whether the current one succeeded or failed. ``` Timeline ─────────────────────────────────────────────────────────> @@ -39,7 +47,9 @@ Timeline ─────────────────────── ## Cron Expressions -FlowCron uses the standard 5-field cron format that you may already know from Unix systems: +A cron expression is just five numbers (or wildcards) that define when something should run. + +FlowCron uses the standard 5-field cron format: ``` ┌───────────── minute (0-59) @@ -70,17 +80,32 @@ When you specify both day-of-month and day-of-week (not `*`), the job runs if ** ## Setup -Setting up a cron job involves creating a handler for your logic, wrapping it with `FlowCron` and scheduling the first tick. All transactions and scripts referenced below are available in the [FlowCron GitHub repository]. +Setting up a cron job involves four steps: + +1. **Create a handler**: Write the code you want to run on each tick +2. **Wrap it with FlowCron**: Connect your handler to a cron schedule +3. **Start the schedule**: Kick off the first execution +4. **Monitor**: Check that everything is running + +All transactions and scripts referenced below are available in the [FlowCron GitHub repository]. + +### Prerequisites Before you start, make sure you have: -- Flow CLI installed -- Some FLOW for transaction fees -- A [TransactionHandler] containing your recurring logic +- **[Flow CLI]** installed: This is the command-line tool you'll use to deploy contracts, send transactions, and run scripts. If you don't have it yet, follow the [installation guide]. +- **FLOW tokens** for transaction fees: Every transaction costs a small amount of FLOW. Get free testnet FLOW from the [Faucet]. +- A **Flow account**: The CLI will help you create one if you don't have one yet. ### 1. Create Your Handler -Create a contract that implements the `TransactionHandler` interface: +First, you need to write the code that will run on each scheduled tick. In Cadence, this is called a **TransactionHandler**. A TransactionHandler is a resource that implements the `FlowTransactionScheduler.TransactionHandler` interface. + +The key part is the `executeTransaction` function. This is where you put whatever logic you want to run on schedule: updating state, distributing tokens, checking conditions, etc. + +For more details on how handlers work, see the [Scheduled Transactions documentation](scheduled-transactions.md#create-a-scheduled-transaction). + +Here's a simple example contract: ```cadence import "FlowTransactionScheduler" @@ -117,11 +142,29 @@ access(all) contract MyRecurringTask { } ``` -Deploy this and save a handler instance to storage. See [CounterTransactionHandler.cdc] for a working example. +This example handler simply logs the timestamp when executed. Replace the `log` statement with your own logic. + +#### Deploy Your Contract + +Use the Flow CLI to deploy your TransactionHandler contract: + +```bash +flow project deploy --network=testnet +``` + +This command reads your `flow.json` configuration and deploys all configured contracts. If you're new to deploying, see the [deployment guide] for a complete walkthrough. + +#### Create and Store a Handler Instance + +After deploying, you need to create an instance of your handler and save it to your account's **storage**. The **storage** is an area in your account where you can save resources (like your handler) that persist between transactions. + +See [CounterTransactionHandler.cdc] for a complete working example that includes the storage setup. ### 2. Wrap It with `FlowCron` -Create a CronHandler that wraps your handler with a cron expression: +Now you need to wrap your handler with a `CronHandler`. This connects your handler to a cron schedule. + +The following transaction creates a new `CronHandler` resource that holds your cron expression and a reference to your handler: ```cadence transaction( @@ -147,18 +190,23 @@ transaction( } ``` -See [CreateCronHandler.cdc] for the full transaction. Run it with: +See [CreateCronHandler.cdc] for the full transaction. + +**Send this transaction using the Flow CLI:** ```bash flow transactions send CreateCronHandler.cdc \ "*/5 * * * *" \ /storage/MyRecurringTaskHandler \ - /storage/MyCronHandler + /storage/MyCronHandler \ + --network=testnet ``` +The arguments are: your cron expression, the storage path where your handler lives, and the path where the new `CronHandler` will be stored. + ### 3. Start the Schedule -Schedule the first executor and keeper to kick off the perpetual loop: +This transaction schedules the first executor and keeper, which kicks off the self-perpetuating loop. After this, your cron job runs automatically: ```cadence transaction( @@ -194,7 +242,9 @@ transaction( } ``` -See [ScheduleCronHandler.cdc] for the full transaction. Run it with: +See [ScheduleCronHandler.cdc] for the full transaction. + +**Send this transaction using the Flow CLI:** ```bash flow transactions send ScheduleCronHandler.cdc \ @@ -202,7 +252,8 @@ flow transactions send ScheduleCronHandler.cdc \ nil \ 2 \ 500 \ - 2500 + 2500 \ + --network=testnet ``` **Parameters:** @@ -215,9 +266,21 @@ flow transactions send ScheduleCronHandler.cdc \ | `executorExecutionEffort` | Computation units for your code (start with `500`) | | `keeperExecutionEffort` | Computation units for keeper (use `2500`) | +:::warning[Fees] + +Starting a cron job requires prepaying fees for the scheduled transactions. FLOW will be deducted from your account to cover the executor and keeper fees. Make sure you have enough FLOW before running this transaction. + +::: + +Once this transaction succeeds, **your cron job is live**. The first execution will happen at the next cron tick, and the schedule will continue automatically from there. You don't need to do anything else, unless you want to monitor it or stop it. + ### 4. Check Status -Query your cron job's metadata with [GetCronInfo.cdc]: +Use Cadence **scripts** to check your cron job's status. Scripts are read-only queries that inspect blockchain state without submitting a transaction—they're free to run and don't modify anything. Learn more in the [scripts documentation]. + +#### Query Cron Info + +The [GetCronInfo.cdc] script returns metadata about your cron handler: ```cadence access(all) fun main(handlerAddress: Address, handlerStoragePath: StoragePath): FlowCron.CronInfo? { @@ -229,11 +292,17 @@ access(all) fun main(handlerAddress: Address, handlerStoragePath: StoragePath): } ``` +**Run this script using the Flow CLI:** + ```bash -flow scripts execute GetCronInfo.cdc 0xYourAddress /storage/MyCronHandler +flow scripts execute GetCronInfo.cdc 0xYourAddress /storage/MyCronHandler --network=testnet ``` -Calculate when the next tick will occur with [GetNextExecutionTime.cdc]: +If your cron job is running, you'll see output showing the cron expression, next scheduled execution time, and handler status. + +#### Calculate Next Execution Time + +The [GetNextExecutionTime.cdc] script calculates when your cron expression will next trigger: ```cadence access(all) fun main(cronExpression: String, afterUnix: UInt64?): UFix64? { @@ -247,18 +316,27 @@ access(all) fun main(cronExpression: String, afterUnix: UInt64?): UFix64? { } ``` +**Run this script using the Flow CLI:** + ```bash -flow scripts execute GetNextExecutionTime.cdc "*/5 * * * *" nil +flow scripts execute GetNextExecutionTime.cdc "*/5 * * * *" nil --network=testnet ``` -Additional scripts for debugging: +#### Additional Debugging Scripts -- [GetCronScheduleStatus.cdc] — Returns executor/keeper IDs, timestamps, and status +- [GetCronScheduleStatus.cdc] — Returns executor/keeper transaction IDs, timestamps, and current status - [GetParsedCronExpression.cdc] — Validates and parses a cron expression into a `CronSpec` ## Stopping a Cron Job -To stop a running cron job, cancel both the executor and keeper transactions: +You might want to stop a cron job for several reasons: + +- **Debugging**: Something isn't working and you need to investigate +- **Updating**: You want to change the schedule or handler logic +- **Cost**: You no longer need the recurring execution +- **Temporary pause**: You want to stop temporarily and restart later + +To stop a running cron job, you need to cancel both the pending executor and keeper transactions. This transaction retrieves the scheduled transaction IDs from your `CronHandler` and cancels them: ```cadence transaction(cronHandlerStoragePath: StoragePath) { @@ -284,10 +362,12 @@ transaction(cronHandlerStoragePath: StoragePath) { } ``` -See [CancelCronSchedule.cdc] for the full transaction. Run it with: +See [CancelCronSchedule.cdc] for the full transaction. + +**Send this transaction using the Flow CLI:** ```bash -flow transactions send CancelCronSchedule.cdc /storage/MyCronHandler +flow transactions send CancelCronSchedule.cdc /storage/MyCronHandler --network=testnet ``` Cancelling refunds 50% of the prepaid fees back to your account. @@ -311,6 +391,11 @@ FlowCron is deployed on both Testnet and Mainnet: [FlowCron GitHub repository]: https://github.com/onflow/flow-cron [Scheduled Transactions Documentation]: scheduled-transactions.md [TransactionHandler]: scheduled-transactions.md#create-a-scheduled-transaction +[Flow CLI]: ../../tools/flow-cli/index.md +[installation guide]: ../../tools/flow-cli/install.md +[Faucet]: https://faucet.flow.com/ +[deployment guide]: ../smart-contracts/deploying.md +[scripts documentation]: ../basics/scripts.md From b388ce9beb48c948c2d02cafea898d66c39d192a Mon Sep 17 00:00:00 2001 From: mfbz Date: Thu, 15 Jan 2026 18:19:22 +0100 Subject: [PATCH 9/9] Highlighted FlowCron text --- docs/build/cadence/advanced-concepts/flow-cron.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/build/cadence/advanced-concepts/flow-cron.md b/docs/build/cadence/advanced-concepts/flow-cron.md index 58686fc59c..63d16c86af 100644 --- a/docs/build/cadence/advanced-concepts/flow-cron.md +++ b/docs/build/cadence/advanced-concepts/flow-cron.md @@ -14,19 +14,19 @@ For example, with the cron expression `0 0 * * *` (daily at midnight), your tran :::info -FlowCron builds on Flow's Scheduled Transactions. If you haven't worked with scheduled transactions before, check out the [Scheduled Transactions documentation](scheduled-transactions.md) first. +`FlowCron` builds on Flow's Scheduled Transactions. If you haven't worked with scheduled transactions before, check out the [Scheduled Transactions documentation](scheduled-transactions.md) first. ::: ## How It Works -FlowCron provides a `CronHandler` resource that wraps your existing [TransactionHandler]. You give it a cron expression (like `*/5 * * * *` for every 5 minutes) and your handler, and FlowCron takes care of the rest. Once started, your schedule runs indefinitely without any further action from you. +`FlowCron` provides a `CronHandler` resource that wraps your existing [TransactionHandler]. You give it a cron expression (like `*/5 * * * *` for every 5 minutes) and your handler, and `FlowCron` takes care of the rest. Once started, your schedule runs indefinitely without any further action from you. ### Why Two Transactions? A key challenge with recurring schedules is fault tolerance: what happens if your code has a bug? You don't want one failed execution to break the entire schedule. -FlowCron solves this by running two separate transactions each time your cron triggers: +`FlowCron` solves this by running two separate transactions each time your cron triggers: - **Executor**: Runs your code. If your logic fails, only this transaction reverts. - **Keeper**: Schedules the next cycle. Runs independently, so even if your code throws an error, the schedule continues. @@ -49,7 +49,7 @@ Timeline ─────────────────────── A cron expression is just five numbers (or wildcards) that define when something should run. -FlowCron uses the standard 5-field cron format: +`FlowCron` uses the standard 5-field cron format: ``` ┌───────────── minute (0-59) @@ -374,12 +374,12 @@ Cancelling refunds 50% of the prepaid fees back to your account. ## Contract Addresses -FlowCron is deployed on both Testnet and Mainnet: +`FlowCron` is deployed on both Testnet and Mainnet: | Contract | Testnet | Mainnet | | --- | --- | --- | -| FlowCron | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | -| FlowCronUtils | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | +| `FlowCron` | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | +| `FlowCronUtils` | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` | ## Resources