Gelato ended Web3 Functions and Automate on March 31, 2026. Every task created there stopped: the triggers, the recurring jobs, the logs. If a contract of yours depended on one, it has been waiting for an executor since spring.
We run Thyme Flow, which does the same kind of job: a TypeScript function that reads the chain, decides, and returns the calls to send. This post shows a real port, the few places where things differ, and how to hand the whole job to a coding agent.
The short version
Your decision logic does not change. Three things do:
- The entry point:
Web3Function.onRun(...)becomesdefineTask({ schema, run }). - The provider: ethers via
multiChainProviderbecomesctx.client, a read-only viem client. - One field name:
callDatabecomescalls.
{ canExec: false, message } stays exactly as it was.
Before: a Gelato oracle function
import { Web3Function, Web3FunctionContext } from '@gelatonetwork/web3-functions-sdk'
import { Contract } from '@ethersproject/contracts'
const ORACLE_ABI = [
'function lastUpdated() view returns (uint256)',
'function updatePrice(uint256)',
]
Web3Function.onRun(async (context: Web3FunctionContext) => {
const { userArgs, multiChainProvider, secrets } = context
const provider = multiChainProvider.default()
const oracle = new Contract(userArgs.oracle as string, ORACLE_ABI, provider)
const lastUpdated = Number(await oracle.lastUpdated())
if (Date.now() / 1000 < lastUpdated + 3600) {
return { canExec: false, message: 'Time not elapsed' }
}
const price = await fetchPrice(await secrets.get('PRICE_API_KEY'))
return {
canExec: true,
callData: [{
to: userArgs.oracle as string,
data: oracle.interface.encodeFunctionData('updatePrice', [price]),
}],
}
})
After: the same function on Thyme Flow
import { defineTask, z } from '@thyme-labs/sdk'
import { encodeFunctionData, parseAbi } from 'viem'
const abi = parseAbi([
'function lastUpdated() view returns (uint256)',
'function updatePrice(uint256)',
])
export default defineTask({
schema: z.object({ oracle: z.address() }),
async run(ctx) {
const lastUpdated = await ctx.client.readContract({
address: ctx.args.oracle,
abi,
functionName: 'lastUpdated',
})
if (Date.now() / 1000 < Number(lastUpdated) + 3600) {
return { canExec: false, message: 'Time not elapsed' }
}
const price = await fetchPrice(ctx.secrets.PRICE_API_KEY)
return {
canExec: true,
calls: [{
to: ctx.args.oracle,
data: encodeFunctionData({
abi,
functionName: 'updatePrice',
args: [price],
}),
}],
}
},
})
Same check, same API call, same call returned. The arguments are now validated by a Zod schema before run starts, and the console renders a form from it.
Mapping the rest
| Gelato | Thyme Flow |
|---|---|
userArgs + schema.json
|
ctx.args, typed by Zod |
await storage.get(key) / storage.set(key, value)
|
ctx.storage.key = value (plain JSON, not only strings) |
await secrets.get('KEY') |
ctx.secrets.KEY |
npx w3f test with userArgs.json
|
thyme run <task> with args.json
|
npx w3f deploy, then create a task from the CID |
thyme upload <task> --tag v1, then create an executable |
| Time interval and cron triggers | Interval (30 seconds to one day) and cron |
| On-chain event or every-block triggers | An interval that polls, with a block cursor in ctx.storage
|
| Dedicated msg.sender | Your own Safe, through a role limited to the calls you allow |
Where it is different
- Calls come from a Safe you own. Gelato sent calls from its dedicated msg.sender. Flow sends them through a Zodiac Roles role on your Safe, limited to the contracts and functions you approve. Remove the role and access is gone. If your contract only accepted Gelato's sender, allow the Safe instead.
-
No event or block triggers. Poll on a short interval and return
canExec: falsewhen there is nothing new. Skipped runs send nothing. - Gas in USD. Mainnet gas is sponsored and charged to a prepaid USD balance. Testnet gas is free.
Let your coding agent do it
If you use Claude Code, Codex, Cursor or a similar agent, paste this into it from the repository that holds your Gelato functions:
Read https://flow.thymelabs.io/docs/skills/thyme-migrate-gelato/SKILL.md and follow it to port the Gelato Web3 Functions in this repository to Thyme Flow. If you support skills, save that file as a skill named thyme-migrate-gelato. Start by listing the functions and triggers you find, port one, run it locally, and ask me before uploading or scheduling anything.
The skill tells the agent to inventory first, port one function at a time, test each locally, and stop on anything that needs you: a contract that only accepts Gelato, fee transfers to Gelato, calls that send ETH, or a chain that is not enabled. Login approval and Safe permissions always stay with you.
Coming from Chainlink Automation instead?
Chainlink retired Automation v2.1 on July 31, 2026. If your contract implements checkUpkeep and performUpkeep, one small task can call them unchanged. The guide is here: Chainlink Automation to Thyme Flow.
Links
- Full guide with the step list: Gelato Web3 Functions to Thyme Flow
- SDK and CLI (MIT): github.com/thyme-labs/sdk
- Docs: flow.thymelabs.io/docs
Stuck on a port? Send the function to support@thymelabs.io and we'll help you move it.













