Skip to main content

circuit.toml

name = "my-aave-yield-agent"
walletType = "ethereum"
runtimeIntervalMinutes = 60
tagline = "Deposits USDC into Aave V3"
allowedExecutionModes = ["auto"]
version = "0.0.1"

[startingAsset]
network = "ethereum:1"
address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
minimumAmount = "10000000" # 10 USDC (6 decimals)

Example

import { Agent, type AgentContext, type CurrentPosition } from "@circuitorg/agent-sdk";
import { encodeFunctionData, parseAbi } from "viem";

// Ethereum Mainnet addresses
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const AAVE_V3_POOL = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";

const erc20Abi = parseAbi(["function approve(address spender, uint256 amount)"]);
const aavePoolAbi = parseAbi([
  "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)",
  "function withdraw(address asset, uint256 amount, address to)",
]);

async function run(agent: AgentContext): Promise<void> {
  // Find USDC balance
  const usdc = agent.currentPositions.find(
    (p) => p.assetAddress.toLowerCase() === USDC.toLowerCase()
  );
  if (!usdc || BigInt(usdc.currentQty) === 0n) {
    await agent.log("No USDC to deposit");
    return;
  }

  const amount = BigInt(usdc.currentQty);
  await agent.log(`Depositing ${usdc.currentQty} USDC (raw) into Aave V3`);

  // Step 1: Approve Aave Pool to spend USDC (skip if already approved)
  const approvedCheck = await agent.memory.get("aaveApproved");
  const alreadyApproved = approvedCheck.success && approvedCheck.data?.value === "true";

  if (!alreadyApproved) {
    const approveData = encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: [AAVE_V3_POOL, 2n ** 256n - 1n], // max uint256 = approve once
    });

    const approveResult = await agent.signAndSend({
      network: "ethereum:1",
      request: {
        toAddress: USDC,
        data: approveData,
        value: "0",
      },
      message: "Approve USDC for Aave V3",
    });

    if (!approveResult.success) {
      await agent.log(`Approve failed: ${approveResult.error}`, { error: true });
      return;
    }
    await agent.memory.set("aaveApproved", "true");
    await agent.log("USDC approved for Aave V3");
  }

  // Step 2: Supply USDC to Aave V3
  const supplyData = encodeFunctionData({
    abi: aavePoolAbi,
    functionName: "supply",
    args: [USDC, amount, agent.sessionWalletAddress, 0],
  });

  const supplyResult = await agent.signAndSend({
    network: "ethereum:1",
    request: {
      toAddress: AAVE_V3_POOL,
      data: supplyData,
      value: "0",
    },
    message: "Supply USDC to Aave V3",
  });

  if (supplyResult.success) {
    await agent.log("USDC deposited into Aave V3!");
  } else {
    await agent.log(`Supply failed: ${supplyResult.error}`, { error: true });
  }
}

async function unwind(agent: AgentContext, positions: CurrentPosition[]): Promise<void> {
  // Withdraw all USDC from Aave V3
  const withdrawData = encodeFunctionData({
    abi: aavePoolAbi,
    functionName: "withdraw",
    args: [USDC, 2n ** 256n - 1n, agent.sessionWalletAddress], // max uint256 = withdraw all
  });

  const result = await agent.signAndSend({
    network: "ethereum:1",
    request: {
      toAddress: AAVE_V3_POOL,
      data: withdrawData,
      value: "0",
    },
    message: "Withdraw all USDC from Aave V3",
  });

  if (result.success) {
    await agent.log("Withdrawn all USDC from Aave V3");
  } else {
    await agent.log(`Withdraw failed: ${result.error}`, { error: true });
  }
}

const agent = new Agent({
  runFunction: run,
  unwindFunction: unwind,
});

export default agent.getExport();

How It Works

  1. Check positions: Finds USDC in agent.currentPositions
  2. Approve (once): On first run, approves Aave V3 Pool to spend USDC with max uint256 and stores a flag in memory. Subsequent runs skip this step.
  3. Supply: Deposits USDC into Aave V3 using the Pool’s supply function
  4. Unwind: Withdraws all USDC from Aave V3 using withdraw with max uint256

Notes

  • This agent uses signAndSend to build custom transactions with ABI-encoded calldata. See Custom Transactions and Signing for details.
  • TypeScript uses viem for ABI encoding — add it with bun add viem.
  • Python uses eth-abi — add it with uv pip install eth-abi.
  • Aave V3 Pool address and USDC address shown are for Ethereum Mainnet. Adjust for other networks.
  • The approve + supply pattern is standard for any ERC-20 DeFi deposit.