Treasury Sweep
Collects what accumulates. On each run the task reads the Safe's balance of a token. If the balance exceeds keep by at least minSweep, it returns one transfer of the excess to treasury. Otherwise it skips.
Typical uses are collecting protocol or integrator fees into a main treasury, or draining a collection address once it holds enough to be worth the gas.
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(),
treasury: z.address(),
keep: decimalString,
minSweep: decimalString,
}),
async run(ctx) {
const { token, treasury } = 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: [ctx.account],
})
const keep = parseUnits(ctx.args.keep, decimals)
const minSweep = parseUnits(ctx.args.minSweep, decimals)
const excess = balance > keep ? balance - keep : 0n
ctx.logger.info(`Balance ${formatUnits(balance, decimals)}, excess ${formatUnits(excess, decimals)}`)
if (excess === 0n || excess < minSweep) {
return { canExec: false, message: 'Nothing worth sweeping' }
}
return {
canExec: true,
calls: [
{
to: token,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [treasury, excess],
}),
},
],
}
},
})keep is the balance that stays in the Safe, and minSweep stops the task from paying gas to move dust. Both are decimal strings in token units. To sweep several tokens, create one executable per token.
Arguments
{
"token": "0x...",
"treasury": "0x...",
"keep": "0",
"minSweep": "50"
}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, including one other than treasury. Point treasury at an address you control, and keep only what you intend to sweep in the Safe. See what the role can and cannot limit.
Schedule
A daily cron at 06:00 UTC:
{ "type": "cron", "cronspec": "0 6 * * *" }