Cron jobs and cranks on Solana
Solana has no built-in cron. Production path: permissionless crank ixs, keepers, Switchboard oracle cranks, and Helium TukTuk (task queues + SOL-paid crank turners + cron jobs). Clockwork is historical context. Patterns, incentives, failure modes.
devrels.xyz/a/214short linkBuilders coming from servers expect cron: at 00:00 UTC, run this job. Solana programs do not get wall-clock callbacks. A program only runs when a transaction invokes it. “Cron on chain” therefore means: encode when work is allowed, then arrange for someone to submit a transaction that does the work.
That someone is usually called a crank, keeper, or bot. The on-chain half is almost always a deliberately permissionless (or weakly gated) instruction. If you do not want to run the bot layer yourself, the production-grade shared service to know is TukTuk (Helium) — covered in depth below.
What the runtime actually gives you
- Clock sysvar —
unix_timestamp, slot, epoch. Read it in your program; nothing fires because time moved. - Oracles — prices/events update when their keepers/cranks push (e.g. Switchboard crank accounts). Your program still waits for a tx.
- No sleep / no setInterval inside a program. Long work is split across many crank txs (queue cursor, pagination).
Pattern A — Permissionless crank instruction
The bread-and-butter design for vesting releases, funding rates, epoch-boundary housekeeping, drip distributions, and “process next N queue items.”
// Conceptual — eligibility on chain, anyone may call
pub fn crank_settle(ctx: Context<CrankSettle>) -> Result<()> {
let clock = Clock::get()?;
require!(
clock.unix_timestamp >= ctx.accounts.job.next_due_ts,
JobError::NotDue
);
// do one unit of work (or a bounded batch)
settle_one(&mut ctx.accounts.job)?;
ctx.accounts.job.next_due_ts =
clock.unix_timestamp.saturating_add(ctx.accounts.job.interval_secs);
// optional: pay crank from job vault
// transfer_lamports(job_vault, crank_signer, tip)?;
Ok(())
}Properties that matter:
- Idempotent / single-step — two cranks in the same slot should not double-pay. Use state transitions (status enum, monotonic cursor).
- Bounded CU — never “process entire queue” in one ix; process
kitems and leave a cursor. - Incentive — tip in SOL/tokens, keep residual fees, MEV-adjacent opportunity, or protocol-run bots. Unpaid cranks stall when you stop caring.
- Missed beats — if nobody cranks for 3 intervals, decide: catch-up one step, catch-up all steps, or skip (funding rates often catch up carefully).
Pattern B — Off-chain keeper fleet
Same on-chain crank ix; the scheduler lives in your infra (or the public’s). Examples already in the ecosystem:
| Domain | What gets cranked | Who runs bots |
|---|---|---|
| Jupiter limits / DCA | Fill or step when price/time conditions hold | Jupiter + permissionless keepers |
| Perps / lending | Liquidations, funding, interest index | Specialist liquidators + protocol bots |
| Switchboard | Oracle aggregator updates via crank accounts | Crank operators / open participation where enabled |
| Your app | Whatever next_due_ts allows | You first; open crank if you want resilience |
Off-chain side is boring on purpose: poll RPC or stream, simulate, send with priority fees, retry on blockhash expiry. Solana “cron” in production is often a systemd timer + a funded key + good observability — not a magic program feature.
# Operator sketch (not a product)
# every minute: find due jobs, simulate crank ix, send if sim OK
while true; do
node keeper.js --rpc "$RPC" --keypair ./crank.json
sleep 15
donePattern C — TukTuk (Helium): permissionless crank marketplace
TukTuk is the crucial shared layer for “run this Solana instruction when triggered or on a schedule” without building your own cranker fleet from scratch. Helium built it as a permissionless crank service: you queue tasks onto a task queue; independent operators run tuktuk-crank-turner and earn SOL per crank.
Program ID (main program): tuktukUrfhXT6ZT77QTU8RQtvgL967uRuVagWF57zVA. Apache-2.0 monorepo — programs, Rust SDK, CLI, crank turner, TypeScript examples. Still actively pushed (into 2026).
Architecture
| Piece | Role |
|---|---|
| Task queue | Named queue with capacity, default crank reward, funding vault, queue authorities. 1 SOL min deposit to create (refunded on close) — spam brake so crankers watch fewer queues. |
| Task | Trigger + transaction source (compiled ixs or remote) + optional per-task reward override + description. Closed after run; rent back to creator. |
| Crank turner | cargo install tuktuk-crank-turner — RPC URL + keypair + min_crank_fee. No Geyser/Yellowstone required. |
| CLI | cargo install tuktuk-cli — create/fund queues, list/simulate tasks, cron manage, manual task run. |
| Cron jobs | Schedule that enqueues tasks onto a queue at intervals (cron must stay funded so it can fund child tasks). |
Design intent (from upstream): reuse task queues. Creating one queue per user is an antipattern — crank turners cannot watch infinite queues. Multiple queue authorities can share one queue (Helium’s hpl-crons PDA can queue audited job types; admins can intervene).
Incentives (why turners show up)
- Queue creator sets crank reward in SOL (default and optional per-task override).
- Reward must exceed cranker cost (base fee + priority fees) or the market will ignore your tasks.
- Turners configure
min_crank_feeand skip cheap work. - Recursive tasks (a task that queues more tasks) spend from the queue funding, not only the original wallet — keep queues funded.
# Install
cargo install tuktuk-cli
cargo install tuktuk-crank-turner
# Create a reusable queue (funding-amount is extra to the 1 SOL deposit)
tuktuk -u "$RPC" task-queue create \
--name my-queue \
--capacity 10 \
--funding-amount 100000000 \
--queue-authority "$AUTHORITY" \
--crank-reward 1000000
# Run a turner (config.toml or TUKTUK__* env)
tuktuk-crank-turner -c config.toml
# rpc_url, key_path, min_crank_feeQueueing work
- On-chain CPI —
QueueTaskV0from your program (see repocpi-example+ tests). - TypeScript / Rust SDK —
typescript-examples,tuktuk-sdk. - Triggers — e.g.
nowor time-gated (cron path for schedules). - Compiled vs remote transactions — simple ixs compile ahead of time. Complex graphs (e.g. cNFT proofs) use
remoteV0: cranker POSTs{ task, task_queue, task_queued_at }to your URL; server returns base64 tx + remaining accounts + signature so the program can trust the ix set. - Target endpoints should be mostly permissionless; TukTuk can also supply PDA signatures where designed.
Cron on TukTuk
A TukTuk cron job does not execute your business logic by magic — it queues tasks onto a task queue on a schedule. You fund the cron so it can fund the queue for each spawned task. If funding dies, the cron can fall off the queue (removed_from_queue); cron requeue brings it back. Monitor with cron list / cron get / task list --description "queue …".
Ops reality
- Need a solid RPC (websocket drops kill turners; restart process supervision required).
task listcan simulate pending tasks for debug; raced turners hitting the same task may see benignAccountNotInitializedafter the winner closed the task.- Failed tasks that will never succeed should be
task close’d to free capacity and refund fees. After a bugfix,task runcan force execution when turners have exhausted retries. - Capacity is finite and rent-priced — size queues for reuse, not per-user spam.
Repo: github.com/helium/tuktuk. Related: tuktuk-fanout. DevRels: organisations/tuktuk.
Pattern D — Shared automation history (Clockwork)
Clockwork popularized threads: on-chain objects with a trigger and instruction sets for workers. Useful mental model; the main clockwork-xyz/clockwork repo has been quiet since early 2024. For a live fee market of crank turners in 2026, start with TukTuk, not Clockwork, unless you have verified a deployment you trust.
Pattern E — Specialized automation (Magic Actions, etc.)
Some stacks automate after a specific lifecycle event, not “every N seconds.” MagicBlock Magic Actions attach Solana instructions to run immediately after an Ephemeral Rollup commit, using freshly committed state — automation in the commit path, not a general cron bus.
Use specialized hooks when your trigger is “state just landed,” not “wall clock ticked.”
Design checklist
| Question | Why it bites |
|---|---|
| Who is incentivized if your team is offline? | Unpaid permissionless cranks die quietly |
| What if the crank is 10 intervals late? | Accounting bugs, unfair funding, insolvent vaults |
| Can two cranks race? | Double spend / double emit without strict state machine |
| CU and account write locks? | Hot accounts serialize; batch size matters |
| Who is fee payer? | Empty crank wallet = stalled protocol |
| Do you need crank authority at all? | Open crank maximizes liveness; closed crank maximizes control (and single point of failure) |
Minimal architecture that scales
- On-chain work — either your own job account + crank ix, or queue a TukTuk task / cron that calls your permissionless endpoint
- Bounded steps — ≤ N work units per tx; cursor for the rest
- Liveness — DIY keeper and/or TukTuk crank reward high enough for turners
- Metrics — last success on chain + queue depth + crank lag alarms
DIY:
Job { next_due_ts, cursor, tip } → keeper → crank_settle
TukTuk:
task-queue (1 SOL deposit + funding + crank_reward)
↑ queueTask / cron
your program endpoint (permissionless / PDA-assisted)
↑ runTask
tuktuk-crank-turner operators (paid in SOL)What not to do
- Rely on clients to “remember” to call maintenance after every user action — they will not under load or when they churn
- Unbounded loops in one instruction (“drain the whole merkle tree”)
- Hardcode a single EOA crank with no monitoring and no tip path
- Assume a third-party automation network is still live because docs still render — prefer actively maintained stacks (TukTuk) and verify workers
- One TukTuk task queue per user (forces crankers to watch unbounded queues)
Related reading on DevRels
- TukTuk organisation · helium/tuktuk
- Jupiter — limit/DCA keepers
- Switchboard — oracle cranks
- Flash Trade — liquidation-shaped keepers
Summary
Cron on Solana is a contract + economics + bot problem. Put eligibility and safety on chain; get a transaction submitted when due. DIY = permissionless crank ix + your keeper. Shared marketplace = TukTuk task queues with SOL-paid crank turners, cron enqueue, CPI/SDK integration, and optional remote transaction servers. Keep CU bounded, rewards above crank cost, and queues reusable. Clockwork taught the shape; TukTuk is the one to evaluate first for production automation in 2026.
Keep reading
Pyth is not “a price API with a logo” — it is publisher-sourced market data you can verify on-chain, usually by pulling an update into the same transaction that needs the price.
Fake World Assets (FWA.fun) is a reference design for fair NFT allocation — deposit, inverse weights, VRF draw, keep or bid — and how to rebuild it on Solana.
OSS-first shelf for Solana NFT builders: standards and working tools only — then re-check every link before you ship on it.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
