thyme/labs
Start building
Thyme Flow

Contracts that act on a schedule.

Write a TypeScript task that reads the chain and returns the calls to send. Flow runs it on a cron, an interval or a webhook, sends the calls from a Safe you own, and handles the gas.

$ npm install -g @thyme-labs/cli
A simulated minute on PolygonThirty tick marks for the blocks in one minute, white marks where scheduled tasks fire, and green dots where their transactions were included.:00:3021:13:0293,626,991simulated block height
This minute, simulated. Ticks are Polygon blocks, white marks are scheduled triggers, and green dots are transactions that landed.
Illustrative run history · live clock, simulated transactions
ExecutableResult
harvest-rewardsIncluded in 93,626,991
sync-oracleIncluded in 93,626,982
top-up-relayerIncluded in 93,626,975
sync-oracleIncluded in 93,626,967

From your editor to a block in four steps.

Your code decides whether to act and which calls to make. Flow handles the schedule, the signing and the gas.

Follow the quickstart
functions/harvest-rewards/index.ts1 of 4
import { defineTask, z } from '@thyme-labs/sdk'
import { encodeFunctionData, parseAbi } from 'viem'

const abi = parseAbi([
  'function harvest()',
  'function pendingRewards() view returns (uint256)',
])

export default defineTask({
  schema: z.object({
    vault: z.address(),
    minRewards: z.coerce.bigint(),
  }),
  async run(ctx) {
    const { vault, minRewards } = ctx.args
    const pending = await ctx.client.readContract({
      address: vault, abi, functionName: 'pendingRewards',
    })

    // Skip this run: not worth the gas yet.
    if (pending < minRewards) {
      return { canExec: false, message: `${pending} pending` }
    }

    return {
      canExec: true,
      calls: [{
        to: vault,
        data: encodeFunctionData({ abi, functionName: 'harvest' }),
      }],
    }
  },
})

Run it on a clock, or on a call.

Every executable has one schedule. Webhooks and Execute now in the console fire extra runs without touching it.

Cron

0 */6 * * *

One day

Any standard cron expression. The console reads it back in plain words, so you can check "At minute 0 past every 6th hour" before you save.

Interval

every 30s

Six minutes

From every 30 seconds up to once a day, with an optional start time. Useful for polling state that has no event to listen for.

Webhook

POST /hooks/…

Whenever you call it

Up to ten named, secret URLs per executable. Post an empty body to use the saved arguments, or a JSON body that replaces them for that run.

terminal
curl -X POST "$HARVEST_WEBHOOK_URL" \
  -H 'content-type: application/json' \
  -d '{ "vault": "0x8f3C…a21E", "minRewards": "5000000000000000000" }'

The body is checked against the same schema as your task's arguments. Fast runs answer with their result; slower ones return 202 and a URL to collect it from.

Reacting to an on-chain event? Poll for it on an interval and return canExec: false when there is nothing to do. Read about triggers.

The runtime

Read first. Send only when it makes sense.

A task is plain TypeScript. Check live chain state, use its saved arguments, then return the contract calls Flow should execute. A skipped run sends no transaction.

Explore the task context
one run / one decisionTypeScript
ctx.args

Typed inputs

Define a schema once. The console and webhooks validate arguments against it.

ctx.client

Chain reads

Read contract state inside your task before deciding which calls to return.

ctx.secrets

Project secrets

Attach only the values this executable needs, without adding them to your source.

canExec: falseNo call to send. Try again at the next trigger.

Run history

See what happened after the clock fired.

Each execution has a status and a record in the console. Follow it from trigger to transaction, or inspect why it skipped or failed.

  1. 01

    Triggered

    A schedule, webhook or manual run starts the executable.

  2. 02

    Evaluated

    The task reads the chain and either skips or returns calls.

  3. 03

    Submitted

    Flow sends the approved calls using the selected profile.

  4. 04

    Confirmed

    The run records its receipt, transaction hash and outcome.

Skipped and failed runs end before confirmation and remain visible in run history.

The account stays in your hands.

Give Flow permission to execute the calls your task returns. Keep ownership and the ability to remove that permission.

Read about profiles

Permission map

Who controls the account

Flow can submit calls within the permissions set on your Safe. Your wallet remains its owner.Diagram preview only.

Your Safe stays yours

Your wallet owns the Safe. Flow receives a scoped executor role for the calls you allow. You can revoke that role from the Safe.

Inspect before signing

During profile setup, the console checks deployed contract code against pinned hashes before it asks for your signature.

Secrets stay out of your bundle

Bind project secrets in the console. Flow passes their values to the runtime when the executable runs.

Pricing

Start on testnet. Grow into mainnet.

Choose a plan for your workspace. Paid plans add capacity, mainnet access and metered usage above the included compute units.

Free

$0 / mo

Try scheduled work on testnets.

  • 60,000 compute units / month
  • 3 executables
  • 3 function uploads
  • Free testnet gas
Choose Free

Pro

$29 / mo

Put recurring work into production.

  • 600,000 compute units / month
  • 20 executables
  • 25 free RPC requests / execution
  • $5 gas credits included
Choose Pro

Enterprise

$149 / mo

Room for higher volume execution.

  • 6,000,000 compute units / month
  • 100 executables
  • 100 free RPC requests / execution
  • Unlimited function uploads
Choose Enterprise

Free is testnet only. Mainnet gas is charged separately; plan limits and current pricing are shown in the console before you subscribe.

Start here

Write the taskSet the time

terminal
$ npm install -g @thyme-labs/cli

Make your first scheduled executable in the console, or work through the quickstart from your editor.