Balance Top-up
Keeps a wallet funded. On each run the task reads the recipient's balance of a token. If it is below minBalance, the task returns one transfer of amount from the Safe. Otherwise it skips.
Typical uses are a bot or gas wallet that needs a floor of a stablecoin, or a team wallet that spends steadily.
Task
import { defineTask, z } from '@thyme-labs/sdk'
import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from 'viem'
const decimalString = z.string().regex(/^\d+(\.\d+)?$/)
export default defineTask({
schema: z.object({
token: z.address(),
recipient: z.address(),
amount: decimalString,
minBalance: decimalString,
}),
async run(ctx) {
const { token, recipient } = ctx.args
const decimals = await ctx.client.readContract({
address: token,
abi: erc20Abi,
functionName: 'decimals',
})
const balance = await ctx.client.readContract({
address: token,
abi: erc20Abi,
functionName: 'balanceOf',
args: [recipient],
})
ctx.logger.info(`Recipient balance: ${formatUnits(balance, decimals)}`)
if (balance >= parseUnits(ctx.args.minBalance, decimals)) {
return { canExec: false, message: 'Balance is above the minimum' }
}
const amount = parseUnits(ctx.args.amount, decimals)
const available = await ctx.client.readContract({
address: token,
abi: erc20Abi,
functionName: 'balanceOf',
args: [ctx.account],
})
if (available < amount) {
return { canExec: false, message: 'The Safe does not hold enough to top up' }
}
return {
canExec: true,
calls: [
{
to: token,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [recipient, amount],
}),
},
],
}
},
})amount and minBalance are decimal strings in token units, such as "100" or "12.5". The task converts them with the token's own decimals. A threshold in token units is the same as a USD threshold for a stablecoin. Flow has no built-in price source, so a volatile token needs a price feed read added to the task.
Arguments
{
"token": "0x...",
"recipient": "0x...",
"amount": "100",
"minBalance": "25"
}Replace the addresses with the token and recipient on your chain.
Permissions
{
"calls": [
{ "target": { "arg": "token" }, "function": "transfer(address,uint256)" }
]
}Your Safe profile must allow the same token and transfer(address,uint256). A role allowed to call transfer can send any amount to any address, so keep only what the automation needs in the Safe. See what the role can and cannot limit.
Schedule
An interval of five minutes:
{ "type": "interval", "intervalMs": 300000 }Pick an interval that leaves room for confirmation. If the recipient spends the top-up before the next run, the task tops it up again.