{"id":47920576,"url":"https://github.com/onflow/flow-cron","last_synced_at":"2026-04-04T05:57:09.632Z","repository":{"id":327562307,"uuid":"1053649496","full_name":"onflow/flow-cron","owner":"onflow","description":"Cadence contract for cron-like scheduling on Flow.","archived":false,"fork":false,"pushed_at":"2025-12-19T20:43:31.000Z","size":213,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T05:56:35.065Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Cadence","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/onflow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-09T18:29:49.000Z","updated_at":"2025-12-19T20:43:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/onflow/flow-cron","commit_stats":null,"previous_names":["onflow/flow-cron"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/onflow/flow-cron","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onflow%2Fflow-cron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onflow%2Fflow-cron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onflow%2Fflow-cron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onflow%2Fflow-cron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/onflow","download_url":"https://codeload.github.com/onflow/flow-cron/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onflow%2Fflow-cron/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31389392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T05:57:08.886Z","updated_at":"2026-04-04T05:57:09.617Z","avatar_url":"https://github.com/onflow.png","language":"Cadence","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FlowCron - Cron Job Scheduling on Flow\n\nFlowCron enables autonomous, recurring transaction execution without external triggers, allowing smart contracts to \"wake up\" and execute logic at predefined times using cron expressions.\n\n## Overview\n\nFlowCron leverages Flow's native transaction scheduling capabilities (FLIP-330) to implement recurring executions. Unlike traditional cron systems that require external schedulers, FlowCron operates entirely onchain, ensuring decentralization and reliability.\n\n### Key Features\n\n- **Standard Cron Syntax**: Uses familiar 5-field cron expressions (minute, hour, day-of-month, month, day-of-week)\n- **Self-Perpetuating**: Jobs automatically reschedule themselves after each execution\n- **Keeper/Executor Architecture**: Separates scheduling logic from user code for fault isolation\n- **Fault Tolerant**: Executor failures don't stop the keeper from scheduling next cycle\n- **Flexible Priority**: Supports High, Medium, and Low priority executions\n- **View Resolver Integration**: Full support for querying job states and metadata\n- **Distributed Design**: Each user controls their own CronHandler resources\n\n## Contract Addresses\n\n| Contract | Testnet | Mainnet |\n|----------|---------|---------|\n| FlowCron | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` |\n| FlowCronUtils | `0x5cbfdec870ee216d` | `0x6dec6e64a13b881e` |\n\n## Quick Start\n\n### 1. Create a Transaction Handler\n\nFirst, create a handler that implements the `TransactionHandler` interface:\n\n```cadence\nimport \"FlowTransactionScheduler\"\n\naccess(all) resource MyTaskHandler: FlowTransactionScheduler.TransactionHandler {\n    access(FlowTransactionScheduler.Execute)\n    fun executeTransaction(id: UInt64, data: AnyStruct?) {\n        // Your recurring logic here\n        log(\"Cron job executed!\")\n    }\n}\n```\n\n### 2. Wrap with CronHandler\n\nWrap your handler with FlowCron to add scheduling:\n\n```cadence\nimport \"FlowCron\"\nimport \"FlowTransactionScheduler\"\nimport \"FlowTransactionSchedulerUtils\"\nimport \"FlowToken\"\nimport \"FungibleToken\"\n\n// Store your task handler\naccount.storage.save(\u003c-create MyTaskHandler(), to: /storage/MyTaskHandler)\n\n// Create capability to your handler\nlet handlerCap = account.capabilities.storage.issue\u003c\n    auth(FlowTransactionScheduler.Execute) \u0026{FlowTransactionScheduler.TransactionHandler}\n\u003e(/storage/MyTaskHandler)\n\n// Create capabilities for fee payment and scheduling (stored securely in resource)\nlet feeProviderCap = account.capabilities.storage.issue\u003c\n    auth(FungibleToken.Withdraw) \u0026FlowToken.Vault\n\u003e(/storage/flowTokenVault)\n\n// Ensure manager exists\nif account.storage.borrow\u003c\u0026{FlowTransactionSchedulerUtils.Manager}\u003e(\n    from: FlowTransactionSchedulerUtils.managerStoragePath\n) == nil {\n    account.storage.save(\n        \u003c-FlowTransactionSchedulerUtils.createManager(),\n        to: FlowTransactionSchedulerUtils.managerStoragePath\n    )\n}\nlet schedulerManagerCap = account.capabilities.storage.issue\u003c\n    auth(FlowTransactionSchedulerUtils.Owner) \u0026{FlowTransactionSchedulerUtils.Manager}\n\u003e(FlowTransactionSchedulerUtils.managerStoragePath)\n\n// Create cron handler (runs every day at midnight)\n// Capabilities are stored securely in the resource, not passed in transaction data\nlet cronHandler \u003c- FlowCron.createCronHandler(\n    cronExpression: \"0 0 * * *\",\n    wrappedHandlerCap: handlerCap,\n    feeProviderCap: feeProviderCap,\n    schedulerManagerCap: schedulerManagerCap\n)\n\n// Store it\naccount.storage.save(\u003c-cronHandler, to: /storage/MyCronHandler)\n```\n\n### 3. Schedule Initial Execution\n\nUse the provided `ScheduleCronHandler` transaction to start:\n\n```bash\nflow transactions send cadence/transactions/ScheduleCronHandler.cdc \\\n    --arg Path:/storage/MyCronHandler \\\n    --arg 'Optional(String):null' \\\n    --arg UInt8:2 \\\n    --arg UInt64:100 \\\n    --arg UInt64:2500\n```\n\n**Parameters:**\n\n- `cronHandlerStoragePath`: Path to your CronHandler\n- `wrappedData`: Optional data passed to your handler\n- `executorPriority`: Priority for executor (0=High, 1=Medium, 2=Low)\n- `executorExecutionEffort`: Execution effort for user code (100-9999)\n- `keeperExecutionEffort`: Execution effort for keeper scheduling (recommended: 2500)\n\n### 4. Monitor \u0026 Control\n\nQuery status:\n\n```bash\nflow scripts execute cadence/scripts/GetCronInfo.cdc \\\n    --arg Address:0x... \\\n    --arg Path:/storage/MyCronHandler\n```\n\nCancel when needed:\n\n```bash\nflow transactions send cadence/transactions/CancelCronSchedule.cdc \\\n    --arg Path:/storage/MyCronHandler\n```\n\n## Architecture\n\n### Core Components\n\n#### CronHandler Resource\n\nThe main resource that wraps any `TransactionHandler` with cron functionality:\n\n```cadence\naccess(all) resource CronHandler: FlowTransactionScheduler.TransactionHandler, ViewResolver.Resolver\n{\n    // Cron configuration\n    access(self) let cronExpression: String\n    access(self) let cronSpec: FlowCronUtils.CronSpec\n\n    // Wrapped handler\n    access(self) let wrappedHandlerCap: Capability\u003cauth(FlowTransactionScheduler.Execute) \u0026{FlowTransactionScheduler.TransactionHandler}\u003e\n\n    // Capabilities needed for rescheduling\n    access(self) let feeProviderCap: Capability\u003cauth(FungibleToken.Withdraw) \u0026FlowToken.Vault\u003e\n    access(self) let schedulerManagerCap: Capability\u003cauth(FlowTransactionSchedulerUtils.Owner) \u0026{FlowTransactionSchedulerUtils.Manager}\u003e\n\n    // Scheduling state (internal)\n    access(self) var nextScheduledKeeperID: UInt64?\n    access(self) var nextScheduledExecutorID: UInt64?\n\n    // TransactionHandler interface\n    access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?)\n\n    // Public getter methods (all view - read-only)\n    access(all) view fun getCronExpression(): String\n    access(all) view fun getCronSpec(): FlowCronUtils.CronSpec\n    access(all) view fun getNextScheduledKeeperID(): UInt64?\n    access(all) view fun getNextScheduledExecutorID(): UInt64?\n\n    // ViewResolver methods\n    access(all) view fun getViews(): [Type]\n    access(all) fun resolveView(_ view: Type): AnyStruct?\n}\n```\n\n#### ExecutionMode Enum\n\nDetermines whether a scheduled transaction runs as keeper or executor:\n\n```cadence\naccess(all) enum ExecutionMode: UInt8 {\n    access(all) case Keeper   // Pure scheduling logic\n    access(all) case Executor // User code execution\n}\n```\n\n#### CronContext Struct\n\nExecution context passed with each scheduled transaction. This allows scheduling the same CronHandler with different configurations without recreating the resource:\n\n```cadence\naccess(all) struct CronContext {\n    access(contract) let executionMode: ExecutionMode\n    access(contract) let executorPriority: FlowTransactionScheduler.Priority\n    access(contract) let executorExecutionEffort: UInt64\n    access(contract) let keeperExecutionEffort: UInt64\n    access(contract) let wrappedData: AnyStruct?\n}\n```\n\n- `executionMode`: Whether this is a Keeper or Executor transaction\n- `executorPriority`: Priority for executor transactions (High, Medium, Low)\n- `executorExecutionEffort`: Computational effort for user code execution\n- `keeperExecutionEffort`: Computational effort for keeper scheduling operations\n- `wrappedData`: Optional data passed to your handler\n\n#### CronInfo View\n\nMetadata view for querying cron handler information:\n\n```cadence\naccess(all) struct CronInfo {\n    access(all) let cronExpression: String\n    access(all) let cronSpec: FlowCronUtils.CronSpec\n    access(all) let nextScheduledKeeperID: UInt64?\n    access(all) let nextScheduledExecutorID: UInt64?\n    access(all) let wrappedHandlerType: String?\n    access(all) let wrappedHandlerUUID: UInt64?\n}\n```\n\n### Design Principles\n\n#### 1. Keeper/Executor Architecture\n\nFlowCron uses a **dual-mode architecture** that separates scheduling from execution:\n\n```\nTime ────────────────────────────────────\u003e\n     T1                    T2                    T3\n     │                     │                     │\n     ├── Executor ────────►├── Executor ────────►├── Executor\n     │   (user code)       │   (user code)       │   (user code)\n     │                     │                     │\n     └── Keeper ──────────►└── Keeper ──────────►└── Keeper\n         (schedules T2)        (schedules T3)        (schedules T4)\n         (+1s offset)          (+1s offset)          (+1s offset)\n```\n\n**Two transaction types per cycle:**\n\n1. **Executor**: Runs at exact cron tick, executes your wrapped handler\n2. **Keeper**: Runs 1 second later, schedules next cycle (both executor + keeper)\n\n**Why this design?**\n\n- **Fault Isolation**: If executor panics (user code error), keeper still runs and schedules next cycle\n- **No Silent Death**: Keeper uses force-unwrap - if scheduling fails, it panics loudly (better than silent stop)\n- **Strict Priority**: Executor uses exactly the priority you specify - if High priority slot is full, that tick is skipped (use Medium for guaranteed scheduling)\n\n#### 2. Bootstrap Process\n\n**Initial scheduling** (user triggers once):\n\n1. User schedules BOTH executor AND keeper for the first cron tick\n2. Executor runs user code at T1\n3. Keeper schedules next executor (T2) + next keeper (T2+1s)\n4. Cycle continues forever\n\n```\nUser Bootstrap          T1                      T2\n     │                  │                       │\n     ├─ Schedule ──────►├── Executor ──────────►├── Executor\n     │  Executor(T1)    │   runs user code      │   runs user code\n     │                  │                       │\n     └─ Schedule ──────►└── Keeper ────────────►└── Keeper\n        Keeper(T1)          schedules T2            schedules T3\n```\n\n#### 3. Protection Against Duplicate Scheduling\n\nFlowCron tracks `nextScheduledKeeperID` to prevent duplicates:\n\n- If a keeper with different ID tries to execute, it's rejected\n- Emits `CronScheduleRejected` event for monitoring\n- Only the scheduled keeper can continue the chain\n\n#### 4. Distributed Ownership\n\nEach user owns their `CronHandler` resources:\n\n```\n┌─────────────────────────────────────┐\n│         User Account                │\n│                                     │\n│  /storage/MyCronHandler1            │\n│    └─\u003e CronHandler                  │\n│          └─\u003e wraps MyTaskHandler1   │\n│                                     │\n│  /storage/MyCronHandler2            │\n│    └─\u003e CronHandler                  │\n│          └─\u003e wraps MyTaskHandler2   │\n└─────────────────────────────────────┘\n```\n\n**Benefits:**\n\n- No central bottleneck or single point of failure\n- Users pay their own scheduling fees\n- Scales horizontally across all accounts\n- Permissionless - anyone can create cron jobs\n\n### Events\n\nFlowCron emits detailed events for monitoring:\n\n| Event | When Emitted |\n|-------|--------------|\n| `CronKeeperExecuted` | Keeper successfully scheduled next cycle |\n| `CronExecutorExecuted` | Executor successfully ran user code |\n| `CronScheduleRejected` | Duplicate/unauthorized keeper was blocked |\n| `CronScheduleFailed` | Scheduling failed (insufficient funds) |\n| `CronEstimationFailed` | Fee estimation failed (e.g., High priority slot full) |\n\n## Cron Expression Engine\n\n### Syntax\n\nStandard 5-field format:\n\n```\n┌───────────── minute (0-59)\n│ ┌───────────── hour (0-23)\n│ │ ┌───────────── day of month (1-31)\n│ │ │ ┌───────────── month (1-12)\n│ │ │ │ ┌───────────── day of week (0-6, 0=Sunday)\n│ │ │ │ │\n* * * * *\n```\n\n### Operators\n\n- `*` - Any value (wildcard)\n- `,` - List separator: `1,3,5` means 1, 3, and 5\n- `-` - Range: `1-5` means 1, 2, 3, 4, 5\n- `/` - Step: `*/5` means every 5, `10-30/5` means 10, 15, 20, 25, 30\n\n### Common Patterns\n\n| Pattern | Description |\n|---------|-------------|\n| `* * * * *` | Every minute |\n| `*/5 * * * *` | Every 5 minutes |\n| `0 * * * *` | Every hour (on the hour) |\n| `0 0 * * *` | Daily at midnight |\n| `0 12 * * *` | Daily at noon |\n| `0 0 * * 0` | Weekly on Sunday at midnight |\n| `0 0 1 * *` | Monthly on the 1st at midnight |\n| `0 9-17 * * 1-5` | Hourly during business hours (9am-5pm, Mon-Fri) |\n| `*/15 9-17 * * 1-5` | Every 15 min during business hours |\n| `0 0,12 * * *` | Twice daily (midnight and noon) |\n\n### Bitmask Implementation\n\nFlowCronUtils uses **bitmasks** for ultra-efficient scheduling:\n\n```cadence\naccess(all) struct CronSpec {\n    access(all) let minMask: UInt64   // bits 0-59 for minutes\n    access(all) let hourMask: UInt32  // bits 0-23 for hours\n    access(all) let domMask: UInt32   // bits 1-31 for day-of-month\n    access(all) let monthMask: UInt16 // bits 1-12 for months\n    access(all) let dowMask: UInt8    // bits 0-6 for day-of-week\n    access(all) let domIsStar: Bool   // day-of-month was \"*\"\n    access(all) let dowIsStar: Bool   // day-of-week was \"*\"\n}\n```\n\n**Example: `0 9,17 * * 1-5` (9 AM and 5 PM on weekdays)**\n\n```\nminMask:   0x0000000000000001  (bit 0 set = minute 0)\nhourMask:  0x00020200          (bits 9,17 set = hours 9,17)\ndomMask:   0xFFFFFFFE          (all days)\nmonthMask: 0x1FFE              (all months)\ndowMask:   0x3E                (bits 1-5 set = Mon-Fri)\n```\n\n**Benefits:**\n\n- **Space**: ~15 bytes vs hundreds for arrays\n- **Speed**: O(1) bit check vs O(n) array scan\n- **Gas**: Bitwise operations are cheapest on EVM/Cadence\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonflow%2Fflow-cron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fonflow%2Fflow-cron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonflow%2Fflow-cron/lists"}