Aave Supply
Puts idle funds to work. On each run the task reads the Safe's balance of an asset. If it exceeds keep by at least minSupply, the task returns two calls: an approve of the excess to the Aave V3 Pool, then supply of the same amount with the Safe as the recipient of the aToken. Flow sends both as one atomic batch, so the approval and the deposit succeed or fail together.
Task
import { defineTask, z } from '@thyme-labs/sdk'
import { encodeFunctionData, erc20Abi, formatUnits, parseAbi, parseUnits } from 'viem'
const poolAbi = parseAbi([
'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)',
])
const decimalString = z.string().regex(/^\d+(\.\d+)?$/)
export default defineTask({
schema: z.object({
asset: z.address(),
pool: z.address(),
keep: decimalString,
minSupply: decimalString,
}),
async run(ctx) {
const { asset, pool } = ctx.args
const decimals = await ctx.client.readContract({
address: asset,
abi: erc20Abi,
functionName: 'decimals',
})
const balance = await ctx.client.readContract({
address: asset,
abi: erc20Abi,
functionName: 'balanceOf',
args: [ctx.account],
})
const keep = parseUnits(ctx.args.keep, decimals)
const minSupply = parseUnits(ctx.args.minSupply, decimals)
const excess = balance > keep ? balance - keep : 0n
ctx.logger.info(`Balance ${formatUnits(balance, decimals)}, excess ${formatUnits(excess, decimals)}`)
if (excess === 0n || excess < minSupply) {
return { canExec: false, message: 'Nothing worth supplying' }
}
return {
canExec: true,
calls: [
{
to: asset,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [pool, excess],
}),
},
{
to: pool,
data: encodeFunctionData({
abi: poolAbi,
functionName: 'supply',
args: [asset, excess, ctx.account, 0],
}),
},
],
}
},
})pool is the Aave V3 Pool for the market on your chain, and asset must be listed in it. Aave's address book lists each Pool address. The task approves exactly the amount it supplies, so no standing allowance is left. Some tokens, such as USDT on Ethereum, require an allowance of zero before a new one; this template does not handle them.
Arguments
{
"asset": "0x...",
"pool": "0x...",
"keep": "1000",
"minSupply": "500"
}Permissions
{
"calls": [
{ "target": { "arg": "asset" }, "function": "approve(address,uint256)" },
{ "target": { "arg": "pool" }, "function": "supply(address,uint256,address,uint16)" }
]
}Your Safe profile must allow both. Allowing approve is the sensitive one: a role allowed to call it can approve any spender for any amount, whatever the Safe holds at the time, so the exposure includes funds deposited later. An allowance also stays in place after the role is removed; revoke it from the Safe by approving zero. Keep only what you intend to supply in the Safe, and check that pool is the real Pool address. See what the role can and cannot limit.
Schedule
A daily cron at 12:00 UTC:
{ "type": "cron", "cronspec": "0 12 * * *" }The supplied balance accrues in Aave as aTokens held by the Safe. This template does not withdraw. To take funds out, withdraw from the Safe directly.