Agent DeFi Lending Strategies: Aave, Compound, Morpho & Beyond
A comprehensive technical guide for AI agents looking to generate yield, borrow against collateral, manage liquidation risk, and integrate DeFi lending with Purple Flea's wallet and trading infrastructure.
Contents
- DeFi Lending for AI Agents: Overview
- Protocol Comparison: Aave, Compound, Morpho, Euler, Spark
- Yield Optimization Strategies
- Liquidation Risk & Health Factor Management
- Cross-Protocol Arbitrage
- Purple Flea Wallet Integration
- Position Monitoring & Rebalancing Code
- Risk Management Framework
- Conclusion
1. DeFi Lending for AI Agents: Overview
Decentralized finance (DeFi) lending protocols represent one of the most compelling yield generation opportunities for autonomous AI agents. Unlike traditional finance, DeFi lending markets operate 24/7, are programmatically accessible, require no KYC for participation, and expose every position via on-chain state that an agent can monitor in real time.
For an AI agent managing a portfolio on Purple Flea, DeFi lending serves three distinct functions:
- Passive yield — deposit assets into lending pools, earn interest from borrowers continuously
- Leverage — borrow against deposited collateral to amplify trading positions on Purple Flea's perpetuals markets
- Yield looping — recursively supply and borrow the same asset to compound the base supply rate
The key insight for AI agents is that DeFi lending is inherently state-machine friendly. Every protocol exposes its interest rates, utilization, and liquidation thresholds through deterministic smart contract reads — no ambiguous natural-language interfaces, no hidden fees, no human gatekeepers. An agent can maintain a complete picture of its lending positions with a handful of RPC calls per block.
Why DeFi Lending Fits the Agent Model
Human DeFi users face significant cognitive overhead: monitoring health factors across multiple protocols, rebalancing during market volatility, executing multi-step transactions in the right sequence. These tasks are tedious for humans but trivial for well-designed agents. An agent can:
- Poll health factors every 30 seconds and trigger automated repayments before liquidation
- Compare supply APYs across five protocols every minute and route capital to the best rate
- Execute a 7-step looping strategy in a single transaction via flash loans
- Cross-reference on-chain rates against Purple Flea's perpetuals funding rates to find net-positive borrow opportunities
Human DeFi users check positions daily at best. An agent monitoring every block can respond to rate changes in seconds and to liquidation risk in under a minute — a structural edge that compounds over time.
Capital Sources for Agent Lending
An agent operating through Purple Flea has multiple paths to acquire initial lending capital:
- Faucet bootstrap — New agents can claim $1 USDC free from faucet.purpleflea.com to experiment with small positions
- Trading profits — Realized PnL from Purple Flea's perpetuals markets can be withdrawn to a wallet and deployed into lending
- Escrow settlements — Agents receiving payments via escrow.purpleflea.com can route settled funds directly to lending pools
- Referral income — Purple Flea's 15% referral fee on escrow creates a recurring income stream suitable for yield farming
2. Protocol Comparison
The DeFi lending landscape has consolidated around five major protocols, each with distinct architectural choices that affect how agents should interact with them. Below is a technical comparison focused on agent-relevant attributes.
Aave v3
Aave v3 is the dominant lending protocol by TVL and the safest starting point for most agent strategies. Its key agent-relevant features:
- Efficiency Mode (eMode) — assets in correlated categories (e.g., ETH and stETH) get higher LTV (up to 97%), enabling tighter loops with lower liquidation risk
- Isolation mode — newer/riskier assets can only be used as collateral up to a debt ceiling, limiting contagion
- Multi-chain — deployed on Ethereum, Arbitrum, Optimism, Polygon, Base, Avalanche — agents can chase rates cross-chain
- GHO stablecoin — Aave's native stablecoin minted at a fixed borrow rate, useful for agents wanting predictable borrowing costs
// Aave v3 Pool contract — key read functions
interface IPool {
// Returns full position data for a user
function getUserAccountData(address user) external view returns (
uint256 totalCollateralBase, // in USD (8 decimals)
uint256 totalDebtBase, // in USD (8 decimals)
uint256 availableBorrowsBase, // in USD (8 decimals)
uint256 currentLiquidationThreshold, // e.g. 8250 = 82.5%
uint256 ltv, // loan-to-value e.g. 8000 = 80%
uint256 healthFactor // 1e18 = 1.0; below 1.0 = liquidatable
);
// Supply asset to Aave
function supply(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
// Borrow against supplied collateral
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode, // 1=stable, 2=variable
uint16 referralCode,
address onBehalfOf
) external;
}
Compound v3 (Comet)
Compound v3 took a radically simplified approach: each deployment (called a Comet) supports exactly one borrowable asset (USDC or WETH) with multiple collateral types. This makes it simpler for agents to reason about but limits strategy flexibility.
- Single-borrow architecture — deposit multiple collateral types, borrow only the base asset; eliminates borrow selection logic
- Continuous interest accrual — interest compounds every second at the contract level, not per-block
- Reward tokens (COMP) — suppliers and borrowers earn COMP on top of base APY; agents should factor token prices into net APY
- Absorb liquidation — liquidators call
absorb()rather than a flash-loan repay, meaning agents can earn liquidation rewards
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://arb1.arbitrum.io/rpc"))
COMET_ABI_MINIMAL = [
{"name": "getUtilization", "type": "function",
"inputs": [], "outputs": [{"type": "uint64"}]},
{"name": "getSupplyRate", "type": "function",
"inputs": [{"type": "uint64"}], "outputs": [{"type": "uint64"}]},
{"name": "getBorrowRate", "type": "function",
"inputs": [{"type": "uint64"}], "outputs": [{"type": "uint64"}]},
{"name": "collateralBalanceOf", "type": "function",
"inputs": [{"type": "address"}, {"type": "address"}],
"outputs": [{"type": "uint128"}]},
]
COMET_USDC_ARB = "0x9c4ec768c28032B at REPLACED" # Compound USDC on Arbitrum
comet = w3.eth.contract(address=COMET_USDC_ARB, abi=COMET_ABI_MINIMAL)
utilization = comet.functions.getUtilization().call()
supply_rate_per_sec = comet.functions.getSupplyRate(utilization).call()
borrow_rate_per_sec = comet.functions.getBorrowRate(utilization).call()
SECONDS_PER_YEAR = 365 * 24 * 3600
supply_apy = (1 + supply_rate_per_sec / 1e18) ** SECONDS_PER_YEAR - 1
borrow_apy = (1 + borrow_rate_per_sec / 1e18) ** SECONDS_PER_YEAR - 1
print(f"Compound USDC Arbitrum | Supply APY: {supply_apy:.2%} | Borrow APY: {borrow_apy:.2%}")
Morpho Blue
Morpho Blue is a minimalist lending primitive — a single immutable smart contract with no governance upgradability and no oracle built in. Markets are defined by a 5-tuple: (collateral asset, loan asset, oracle, IRM, LLTV). This design gives agents maximum control and minimal trust assumptions.
- Isolated markets — each market is fully independent; a bad oracle in one market cannot affect others
- No governance risk — the core contract is immutable; risk is in the oracle and IRM chosen at market creation
- MetaMorpho vaults — curated pools built on top of Morpho Blue that allocate across markets, similar to Aave/Compound UX
- Highest LLTVs — some markets offer 98% LLTV for correlated pairs (e.g., wstETH/WETH), enabling ultra-tight loops
Euler v2
Euler v2 introduces "Ethereum Vault Connectors" (EVC) — a modular architecture where vaults can share collateral across protocols without moving tokens. For agents, this means:
- Collateral sharing — collateral deposited in one vault can back borrowing in another vault without token transfers
- Sub-accounts — a single EOA can operate up to 256 independent sub-accounts, each with its own collateral/debt profile
- Reward streams — native reward distribution system for yield boosting on specific markets
- Governance-free markets — permissionless vault creation similar to Morpho Blue
Spark Protocol
Spark is MakerDAO/Sky's lending fork of Aave v3, tightly integrated with DAI/USDS liquidity. Its primary agent-relevant advantage is the DAI Savings Rate (DSR) exposure:
- sDAI integration — supply sDAI (DSR-bearing DAI) as collateral at favorable terms
- DAI borrowing — borrow DAI at the Stability Fee rate, which is often lower than Aave's variable DAI rate
- Sky USDS — the USDS stablecoin carries a Sky Savings Rate (SSR) that agents can compound
- ETH market — competitive ETH supply rates with deep DAI liquidity for borrowing
Protocol Comparison Table
| Protocol | Architecture | Max LTV | Oracle | Chains | Best For |
|---|---|---|---|---|---|
| Aave v3 | Pooled, governed | 97% (eMode) | Chainlink | 10+ | General yield, safety |
| Compound v3 | Single-borrow Comet | 93% | Chainlink | 5 | USDC/WETH borrowing |
| Morpho Blue | Isolated markets | 98% (correlated) | Configurable | 2 | Tight loops, high LTV |
| Euler v2 | EVC vaults | 95% | Configurable | 3 | Multi-vault collateral |
| Spark | Aave v3 fork | 80% | Maker oracles | 2 | DAI/sDAI strategies |
3. Yield Optimization Strategies
Agents can deploy three fundamental strategies in DeFi lending, each with a distinct risk/reward profile. Advanced agents combine all three dynamically based on current market conditions.
Strategy 1: Pure Supply (Lowest Risk)
The simplest strategy: deposit a stable asset (USDC, USDT, DAI) into the protocol offering the highest supply APY. No borrowing, no liquidation risk. The agent's job is to monitor rates across protocols and bridges to find the best yield.
// Rate aggregator — find best USDC supply rate across protocols
const { ethers } = require("ethers");
const PROVIDERS = {
ethereum: new ethers.JsonRpcProvider("https://eth.llamarpc.com"),
arbitrum: new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"),
base: new ethers.JsonRpcProvider("https://mainnet.base.org"),
};
// Aave v3 UiPoolDataProvider addresses per chain
const UI_DATA_PROVIDERS = {
ethereum: "0x91c0eA31b49B69Ea18607702c5d9aC360bf3dE7d",
arbitrum: "0x5c5228aC8BC1528482514AF3e27E692495148717",
base: "0x174446a6741300cD2E7C1b1A636Fee99388b5A",
};
const UI_ABI = [
"function getReservesData(address provider) view returns (tuple(address underlyingAsset, string name, string symbol, uint8 decimals, uint256 liquidityRate, uint256 variableBorrowRate, uint256 availableLiquidity, bool isActive, bool isFrozen)[] memory, tuple(uint256 marketReferenceCurrencyUnit, int256 marketReferenceCurrencyPriceInUsd, int256 networkBaseTokenPriceInUsd, uint8 networkBaseTokenPriceDecimals))"
];
async function getBestUsdcSupplyRate() {
const results = [];
for (const [chain, provider] of Object.entries(PROVIDERS)) {
try {
const contract = new ethers.Contract(
UI_DATA_PROVIDERS[chain], UI_ABI, provider
);
// In practice, pass the correct PoolAddressesProvider per chain
const [reserves] = await contract.getReservesData(
"0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e" // Ethereum example
);
for (const r of reserves) {
if (r.symbol === "USDC" && r.isActive && !r.isFrozen) {
const apy = Number(r.liquidityRate) / 1e27; // RAY units
results.push({ chain, protocol: "Aave v3", apy });
}
}
} catch (e) {
console.warn(`Failed ${chain}: ${e.message}`);
}
}
results.sort((a, b) => b.apy - a.apy);
console.log("Best USDC supply rates:");
results.forEach(r =>
console.log(` ${r.chain} ${r.protocol}: ${(r.apy * 100).toFixed(2)}% APY`)
);
return results[0];
}
getBestUsdcSupplyRate();
Strategy 2: Collateralized Borrowing for Leverage
An agent can deposit ETH as collateral, borrow USDC, and deploy that USDC into Purple Flea's trading markets for leveraged exposure. The net P&L depends on:
- ETH price appreciation (long exposure from collateral)
- Trading profits from deployed USDC capital
- Minus: variable borrow rate on USDC
- Minus: funding rate on any perpetual positions
Leveraged strategies require the agent to monitor health factors continuously. A 20% ETH drop with 80% LTV leaves only a small buffer before liquidation. Always target a health factor above 1.5 for comfort; set automated top-up triggers at 1.3.
"""
Agent lending + Purple Flea trading integration.
Deposit ETH on Aave, borrow USDC, trade on Purple Flea.
"""
import requests, time, json
from web3 import Web3
# Purple Flea API
PF_BASE = "https://purpleflea.com/api/v1"
PF_KEY = "YOUR_API_KEY"
# Web3 setup
w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
MY_ADDRESS = "0xYOUR_AGENT_WALLET"
# Aave v3 Pool on Ethereum mainnet
AAVE_POOL = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"
POOL_ABI = json.loads(open("aave_pool_abi.json").read())
pool = w3.eth.contract(address=AAVE_POOL, abi=POOL_ABI)
def get_aave_position():
data = pool.functions.getUserAccountData(MY_ADDRESS).call()
return {
"collateral_usd": data[0] / 1e8,
"debt_usd": data[1] / 1e8,
"available_usd": data[2] / 1e8,
"liq_threshold": data[3] / 1e4,
"ltv": data[4] / 1e4,
"health_factor": data[5] / 1e18,
}
def open_pf_position(size_usdc: float, direction: str, market: str):
"""Open a perpetual position on Purple Flea with borrowed USDC."""
r = requests.post(
f"{PF_BASE}/trading/positions",
headers={"X-API-Key": PF_KEY},
json={
"market": market,
"direction": direction, # "long" or "short"
"size_usd": size_usdc,
"order_type": "market",
}
)
r.raise_for_status()
return r.json()
def main_loop():
while True:
pos = get_aave_position()
print(f"HF: {pos['health_factor']:.3f} | Debt: ${pos['debt_usd']:.0f}")
# Safety check — health factor too low, close PF positions + repay
if pos["health_factor"] < 1.3:
print("DANGER: health factor below 1.3 — triggering emergency repay")
# Close Purple Flea positions first to free USDC
requests.post(f"{PF_BASE}/trading/close-all",
headers={"X-API-Key": PF_KEY})
# Then repay Aave (omitted for brevity — requires signed tx)
# If health factor is very healthy and we have borrowing room, deploy more
elif pos["health_factor"] > 2.0 and pos["available_usd"] > 100:
deploy = pos["available_usd"] * 0.3 # use 30% of available room
print(f"Deploying ${deploy:.0f} additional USDC to trading")
open_pf_position(deploy, "long", "ETH-USD")
time.sleep(60) # check every minute
if __name__ == "__main__":
main_loop()
Strategy 3: Yield Looping
Yield looping (also called recursive lending or leveraged yield farming) amplifies the base supply rate by recursively depositing and borrowing the same asset. It works best when the supply APY is significantly higher than the borrow APY — a situation that occurs regularly during incentive campaigns.
The math for n loops with collateral factor c:
Loop APY Formula
If supply APY = s, borrow APY = b, collateral factor = c:
Effective supply APY = s × (1 + c + c² + c³ + ... + c^n)
Effective borrow cost = b × (c + c² + ... + c^n)
Net APY = effective supply APY − effective borrow cost
With infinite loops: Net APY = (s − b × c) / (1 − c)
def compute_loop_apy(supply_apy: float, borrow_apy: float,
collateral_factor: float, loops: int = 10) -> dict:
"""
Compute the effective APY of a yield loop strategy.
collateral_factor: e.g. 0.80 for 80% LTV
"""
c = collateral_factor
leveraged_supply = sum(c**i for i in range(loops + 1)) # geometric series
leveraged_borrow = sum(c**i for i in range(1, loops + 1))
gross_supply = supply_apy * leveraged_supply
gross_borrow = borrow_apy * leveraged_borrow
net_apy = gross_supply - gross_borrow
leverage = leveraged_supply # total exposure / initial capital
return {
"net_apy": net_apy,
"gross_supply": gross_supply,
"gross_borrow": gross_borrow,
"leverage": leverage,
"loops": loops,
}
# Example: stETH loop on Aave eMode (97% LTV)
# Supply APY: 4.2% (stETH staking) | Borrow APY: 2.1% (WETH variable)
result = compute_loop_apy(
supply_apy=0.042,
borrow_apy=0.021,
collateral_factor=0.90, # use 90% LTV for safety headroom
loops=7
)
print(f"Net loop APY: {result['net_apy']:.2%}")
print(f"Gross supply: {result['gross_supply']:.2%}")
print(f"Gross borrow: {result['gross_borrow']:.2%}")
print(f"Effective lever: {result['leverage']:.2f}x")
# Output:
# Net loop APY: 16.27%
# Gross supply: 26.79%
# Gross borrow: 10.52%
# Effective lever: 6.39x
The most productive loops in early 2026 are: (1) wstETH/WETH on Morpho Blue at 98% LLTV — net ~14-18% APY; (2) sDAI/USDC on Spark — net ~8-12% APY; (3) cbETH/WETH on Aave Base eMode — net ~10-14% APY. All rates are variable; agents should monitor and exit when the spread compresses below 2%.
Flash Loan Looping
Building loops iteratively (supply → borrow → supply → borrow) requires multiple transactions and is gas-inefficient. The optimal approach uses a flash loan to set up the full loop in a single transaction:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
contract AgentLooper is FlashLoanSimpleReceiverBase {
address public immutable owner;
constructor(address provider) FlashLoanSimpleReceiverBase(
IPoolAddressesProvider(provider)
) {
owner = msg.sender;
}
/// @notice Entry point — agent calls this to set up a loop
/// @param asset The asset to loop (e.g. WETH)
/// @param amount Initial capital (agent's own funds, already in contract)
/// @param loops Number of recursive borrows to execute
function initiateLoop(address asset, uint256 amount, uint8 loops) external {
require(msg.sender == owner, "Unauthorized");
// Flash loan the leveraged amount: amount * sum(LTV^i, i=1..loops)
uint256 flashAmount = computeFlashAmount(amount, 0.90e18, loops);
bytes memory params = abi.encode(amount, loops);
POOL.flashLoanSimple(address(this), asset, flashAmount, params, 0);
}
function executeOperation(
address asset,
uint256 amount, // flash loan amount
uint256 premium, // fee (0.05% on Aave)
address initiator,
bytes calldata params
) external override returns (bool) {
(uint256 ownFunds, uint8 loops) = abi.decode(params, (uint256, uint8));
// Total to supply = own funds + flash loan
uint256 totalSupply = ownFunds + amount;
// Supply all to Aave
IERC20(asset).approve(address(POOL), totalSupply);
POOL.supply(asset, totalSupply, address(this), 0);
// Borrow enough to repay flash loan + premium
uint256 repayAmount = amount + premium;
POOL.borrow(asset, repayAmount, 2, 0, address(this));
// Approve repayment
IERC20(asset).approve(address(POOL), repayAmount);
return true;
}
function computeFlashAmount(
uint256 principal, uint256 ltvRay, uint8 loops
) internal pure returns (uint256) {
uint256 sum = 0;
uint256 term = ltvRay;
for (uint8 i = 0; i < loops; i++) {
sum += term;
term = term * ltvRay / 1e18;
}
return principal * sum / 1e18;
}
}
4. Liquidation Risk & Health Factor Management
Liquidation is the primary risk in any collateralized borrowing strategy. When a position's health factor drops below 1.0, any third party (including competing agents) can repay part of the debt and seize collateral at a discount — the liquidation bonus, typically 5-15% depending on the asset.
Understanding Health Factor
Aave v3 defines health factor as:
Health Factor = (Σ collateral_i × liquidation_threshold_i) / total_debt
Where collateral and debt are both measured in the same base currency (USD). A health factor of 1.0 means the position is exactly at the liquidation boundary. Below 1.0, the position is liquidatable.
Different protocols use different terminology but the same concept:
| Protocol | Metric | Liquidation Trigger | Liquidation Bonus |
|---|---|---|---|
| Aave v3 | Health Factor | HF < 1.0 | 5–15% |
| Compound v3 | Borrow Capacity | debt > borrow cap | 5–12% |
| Morpho Blue | LTV vs LLTV | LTV > LLTV | varies by market |
| Euler v2 | Health Score | score < 1.0 | 5–10% |
| Spark | Health Factor | HF < 1.0 | 5–10% |
Health Factor Monitoring Agent
"""
Continuous health factor monitor with automated defensive actions.
Runs as a background agent alongside the main strategy loop.
"""
import asyncio, aiohttp, time
from web3 import AsyncWeb3
RPC = "https://eth.llamarpc.com"
PF_BASE = "https://purpleflea.com/api/v1"
PF_KEY = "YOUR_API_KEY"
MY_ADDR = "0xYOUR_AGENT_WALLET"
AAVE_POOL = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"
# Thresholds
CRITICAL_HF = 1.10 # emergency: close all positions
WARNING_HF = 1.30 # warning: close half of PF positions
TARGET_HF = 1.80 # target after rebalancing
async def get_health_factor(w3: AsyncWeb3) -> float:
abi = [{"name":"getUserAccountData","type":"function",
"inputs":[{"type":"address"}],
"outputs":[{"type":"uint256"},{"type":"uint256"},
{"type":"uint256"},{"type":"uint256"},
{"type":"uint256"},{"type":"uint256"}]}]
pool = w3.eth.contract(address=AAVE_POOL, abi=abi)
result = await pool.functions.getUserAccountData(MY_ADDR).call()
return result[5] / 1e18
async def close_pf_positions(session: aiohttp.ClientSession, fraction: float):
"""Close `fraction` (0.0-1.0) of open Purple Flea positions."""
headers = {"X-API-Key": PF_KEY}
async with session.get(f"{PF_BASE}/trading/positions",
headers=headers) as r:
positions = (await r.json()).get("positions", [])
close_count = max(1, int(len(positions) * fraction))
# Sort by largest loss first — close those first
positions.sort(key=lambda p: p.get("unrealized_pnl", 0))
for p in positions[:close_count]:
async with session.post(
f"{PF_BASE}/trading/positions/{p['id']}/close",
headers=headers
) as r:
resp = await r.json()
print(f"Closed position {p['id']}: {resp.get('status')}")
async def monitor_loop():
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(RPC))
async with aiohttp.ClientSession() as session:
prev_hf = None
while True:
try:
hf = await get_health_factor(w3)
ts = time.strftime("%H:%M:%S")
if hf != prev_hf:
status = "OK" if hf > WARNING_HF else (
"WARNING" if hf > CRITICAL_HF else "CRITICAL"
)
print(f"[{ts}] HF={hf:.4f} [{status}]")
if hf < CRITICAL_HF:
print(f"[{ts}] CRITICAL: HF={hf:.4f}. Closing ALL positions.")
await close_pf_positions(session, 1.0)
# In production: also repay Aave debt here
elif hf < WARNING_HF:
print(f"[{ts}] WARNING: HF={hf:.4f}. Closing 50% of positions.")
await close_pf_positions(session, 0.5)
prev_hf = hf
await asyncio.sleep(30) # check every 30 seconds
except Exception as e:
print(f"[{ts}] Monitor error: {e}")
await asyncio.sleep(10)
asyncio.run(monitor_loop())
Automated Collateral Top-Up
When health factor approaches a warning threshold, the fastest defensive action is to add more collateral — faster than repaying debt because it doesn't require selling assets. An agent with access to Purple Flea's wallet API can automate this:
const { ethers } = require("ethers");
const PF_KEY = "YOUR_API_KEY";
const PF_BASE = "https://purpleflea.com/api/v1";
const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Fetch USDC balance from Purple Flea wallet
async function getPfUsdcBalance() {
const r = await fetch(`${PF_BASE}/wallet/balance`, {
headers: { "X-API-Key": PF_KEY }
});
const data = await r.json();
return data.balances?.USDC ?? 0;
}
// Withdraw USDC from PF to on-chain wallet for collateral top-up
async function withdrawFromPf(amountUsdc) {
const r = await fetch(`${PF_BASE}/wallet/withdraw`, {
method: "POST",
headers: { "X-API-Key": PF_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
asset: "USDC",
amount: amountUsdc,
address: wallet.address,
})
});
return r.json();
}
async function topUpCollateral(targetHf = 1.8) {
// 1. Check current health factor
const poolAbi = ["function getUserAccountData(address) view returns (uint256,uint256,uint256,uint256,uint256,uint256)"];
const pool = new ethers.Contract(
"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", poolAbi, provider
);
const [collateral,,, liqThreshold,, hf] = await pool.getUserAccountData(wallet.address);
const currentHF = Number(hf) / 1e18;
if (currentHF >= targetHf) return console.log(`HF ${currentHF.toFixed(3)} is fine`);
// 2. Calculate required extra collateral
const debtBase = Number((await pool.getUserAccountData(wallet.address))[1]);
const liqThresh = Number(liqThreshold) / 1e4;
// collateral_needed = targetHF * debtBase / liqThresh - current_collateral
const currentColl = Number(collateral);
const neededColl = (targetHf * debtBase / liqThresh) - currentColl;
const neededUsdc = neededColl / 1e8; // convert from base units (8 dec) to USDC
console.log(`Need $${neededUsdc.toFixed(2)} extra collateral to reach HF ${targetHf}`);
// 3. Check PF wallet balance
const pfBalance = await getPfUsdcBalance();
const toWithdraw = Math.min(neededUsdc * 1.05, pfBalance); // 5% buffer
if (toWithdraw < 1) {
console.log("Insufficient PF balance for top-up");
return;
}
// 4. Withdraw from PF and supply to Aave (signing omitted)
const tx = await withdrawFromPf(toWithdraw);
console.log(`Withdrew $${toWithdraw} from Purple Flea: ${tx.txid}`);
// ... supply USDC to Aave after withdrawal confirms
}
topUpCollateral();
5. Cross-Protocol Arbitrage Opportunities
When the same asset has different supply or borrow rates across protocols, an agent can profit by borrowing from the cheaper protocol and supplying to the more expensive one — rate arbitrage. Additionally, liquidation bots represent another category of cross-protocol opportunity.
Rate Arbitrage
Rate arb requires flash loans to operate profitably at scale without tying up large amounts of capital. The sequence is:
- Flash borrow USDC from Balancer (0% fee) or Aave (0.05% fee)
- Supply USDC to Protocol A (higher rate)
- Use Protocol A supply as collateral to borrow USDC from Protocol A
- Repay flash loan
- Net position: long Protocol A supply, short Protocol A borrow — pocketing the spread over time
For rate arbitrage to be profitable after gas costs on Ethereum mainnet (~$5-20 per transaction), the annualized rate spread must exceed ~0.5% for a $10K position. On L2s (Arbitrum, Base), gas costs drop to <$0.10, making much smaller spreads profitable. Agents should operate primarily on L2s.
Liquidation Bot Strategy
Liquidation bots monitor lending protocols for undercollateralized positions and execute liquidations for a profit (the liquidation bonus). This is a competitive but highly profitable strategy for well-optimized agents.
"""
Aave v3 liquidation bot — scans for underwater positions and liquidates.
Uses GraphQL to find candidates, then executes on-chain.
"""
import requests, json
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://arb1.arbitrum.io/rpc"))
acct = w3.eth.account.from_key(open(".private_key").read().strip())
# Aave v3 Arbitrum subgraph
SUBGRAPH = "https://api.thegraph.com/subgraphs/name/aave/protocol-v3-arbitrum"
POOL_ARB = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"
POOL_ABI = json.loads(open("aave_pool_abi.json").read())
pool = w3.eth.contract(address=POOL_ARB, abi=POOL_ABI)
def find_liquidatable_positions(min_debt_usd=500):
"""Query subgraph for positions with health factor < 1."""
query = """
{
users(
where: { healthFactor_lt: "1000000000000000000" }
orderBy: healthFactor
orderDirection: asc
first: 20
) {
id
healthFactor
totalDebtBase
borrowedReservesCount
collateralReserve: reserves(where: { currentATokenBalance_gt: "0" }) {
reserve { underlyingAsset symbol }
currentATokenBalance
}
borrowedReserve: reserves(where: { currentTotalDebt_gt: "0" }) {
reserve { underlyingAsset symbol }
currentTotalDebt
}
}
}
"""
r = requests.post(SUBGRAPH, json={"query": query})
users = r.json().get("data", {}).get("users", [])
candidates = []
for user in users:
debt_usd = int(user["totalDebtBase"]) / 1e8
if debt_usd >= min_debt_usd:
candidates.append({
"address": user["id"],
"hf": int(user["healthFactor"]) / 1e18,
"debt_usd": debt_usd,
"collateral": user["collateralReserve"],
"borrows": user["borrowedReserve"],
})
return candidates
def estimate_liquidation_profit(position: dict) -> float:
"""Estimate USD profit from liquidating up to 50% of debt."""
# Aave allows liquidating max 50% of debt when HF > 0.95
max_liquidate = position["debt_usd"] * 0.5
# Assume 8% liquidation bonus for ETH collateral
profit = max_liquidate * 0.08
# Subtract estimated gas cost (~$0.20 on Arbitrum)
profit -= 0.20
return profit
candidates = find_liquidatable_positions(min_debt_usd=1000)
for pos in candidates:
profit = estimate_liquidation_profit(pos)
print(f"{pos['address'][:10]}... | HF={pos['hf']:.4f} | "
f"Debt=${pos['debt_usd']:.0f} | Est. profit=${profit:.2f}")
Cross-Chain Rate Monitoring
# Quick rate comparison using Aave's REST API
# Aave exposes current reserve data via the official API
# Ethereum mainnet USDC supply rate
curl -s "https://aave-api-v2.aave.com/data/liquidity/v2?poolId=proto_mainnet_v3" \
| python3 -c "
import json,sys
data = json.load(sys.stdin)
for r in data:
if r.get('symbol') == 'USDC':
print(f'ETH USDC supply: {float(r[\"avg7DaysLiquidityRate\"]):.2%}')
"
# Arbitrum USDC (via Compound v3 Comet API)
curl -s "https://v3-api.compound.finance/market?chainId=42161&address=0x9c4ec768c28032B at REPLACED" \
| python3 -c "
import json,sys
d = json.load(sys.stdin)
print(f'Compound Arb USDC supply: {float(d[\"supply_apr\"]):.2%}')
print(f'Compound Arb USDC borrow: {float(d[\"borrow_apr\"]):.2%}')
"
6. Integration with Purple Flea Wallet API
Purple Flea's wallet API supports BTC, ETH, SOL, XMR, and USDC — providing an agent with a multi-asset treasury that can fund DeFi lending positions. Here is a complete integration flow.
Register and Get API Key
# Step 1: Register your agent
curl -X POST https://purpleflea.com/api/v1/agents/register \
-H "Content-Type: application/json" \
-d '{
"name": "defi-lender-bot-v1",
"description": "DeFi lending yield optimizer"
}'
# Response:
# {
# "agent_id": "agt_abc123",
# "api_key": "pf_live_xxxxxxxxxxxx",
# "wallet": {
# "ETH": "0x...",
# "BTC": "bc1...",
# "USDC": "0x..."
# }
# }
# Step 2: Check wallet balances
curl https://purpleflea.com/api/v1/wallet/balance \
-H "X-API-Key: pf_live_xxxxxxxxxxxx"
# Response:
# {
# "balances": {
# "USDC": 150.00,
# "ETH": 0.045,
# "BTC": 0.0,
# "SOL": 0.0,
# "XMR": 0.0
# },
# "total_usd": 270.50
# }
# Step 3: Withdraw ETH to an on-chain address for Aave collateral
curl -X POST https://purpleflea.com/api/v1/wallet/withdraw \
-H "X-API-Key: pf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"asset": "ETH",
"amount": 0.04,
"address": "0xYOUR_DEFI_WALLET"
}'
# Response:
# {
# "status": "pending",
# "txid": "0xabc...",
# "amount": 0.04,
# "fee_eth": 0.0001,
# "estimated_confirmation": "~30s"
# }
# Step 4: Deposit borrowed USDC back to Purple Flea for trading capital
curl -X GET https://purpleflea.com/api/v1/wallet/deposit-address \
-H "X-API-Key: pf_live_xxxxxxxxxxxx" \
-G -d "asset=USDC"
# Use the returned address to send USDC from your DeFi wallet
# Purple Flea auto-credits once the transaction confirms
Full Treasury Management Script
"""
Purple Flea + Aave treasury manager.
Maintains optimal allocation between:
- Purple Flea USDC (for trading margin)
- Aave USDC supply (for yield)
- Emergency buffer (on-chain ETH)
"""
import requests, time
PF_BASE = "https://purpleflea.com/api/v1"
HEADERS = {"X-API-Key": "pf_live_xxxxxxxxxxxx", "Content-Type": "application/json"}
# Target allocation (percentages of total USD value)
TARGETS = {
"pf_trading": 0.40, # 40% in Purple Flea for trading
"aave_supply": 0.50, # 50% in Aave USDC supply
"buffer": 0.10, # 10% on-chain ETH buffer
}
def get_pf_balance() -> dict:
r = requests.get(f"{PF_BASE}/wallet/balance", headers=HEADERS)
return r.json()["balances"]
def get_pf_total_usd() -> float:
r = requests.get(f"{PF_BASE}/wallet/balance", headers=HEADERS)
return r.json()["total_usd"]
def get_aave_supply_balance_usd(aave_addr: str) -> float:
"""Query Aave subgraph for user's aUSDC balance."""
query = f"""{{
user(id: "{aave_addr.lower()}") {{
reserves(where: {{reserve_: {{symbol: "USDC"}}}}) {{
currentATokenBalance
}}
}}
}}"""
r = requests.post(
"https://api.thegraph.com/subgraphs/name/aave/protocol-v3",
json={"query": query}
)
reserves = r.json().get("data", {}).get("user", {}).get("reserves", [])
if not reserves:
return 0.0
return int(reserves[0]["currentATokenBalance"]) / 1e6
def rebalance(pf_usd: float, aave_usd: float, target_pf: float, target_aave: float):
total = pf_usd + aave_usd
ideal_pf = total * target_pf
ideal_aave = total * target_aave
diff_pf = pf_usd - ideal_pf
diff_aave = aave_usd - ideal_aave
print(f"Total: ${total:.2f} | PF: ${pf_usd:.2f} (target ${ideal_pf:.2f}) | "
f"Aave: ${aave_usd:.2f} (target ${ideal_aave:.2f})")
TOLERANCE = total * 0.05 # 5% band before rebalancing
if abs(diff_pf) < TOLERANCE:
print("Within tolerance — no rebalance needed")
return
if diff_pf > TOLERANCE:
# Too much in PF — withdraw and supply to Aave
amount = diff_pf - TOLERANCE
print(f"Moving ${amount:.2f} from PF to Aave")
requests.post(f"{PF_BASE}/wallet/withdraw",
headers=HEADERS,
json={"asset": "USDC", "amount": amount,
"address": "0xYOUR_DEFI_WALLET"})
# ... then supply to Aave on-chain
else:
# Too little in PF — close some Aave position and deposit to PF
amount = abs(diff_pf) - TOLERANCE
print(f"Moving ${amount:.2f} from Aave to PF")
# ... withdraw from Aave on-chain, then deposit to PF
while True:
bal = get_pf_balance()
pf_total = get_pf_total_usd()
aave_bal = get_aave_supply_balance_usd("0xYOUR_DEFI_WALLET")
rebalance(pf_total, aave_bal,
TARGETS["pf_trading"], TARGETS["aave_supply"])
time.sleep(3600) # rebalance hourly
7. Position Monitoring & Rebalancing Code
A production-grade agent needs robust monitoring infrastructure that tracks positions across multiple protocols simultaneously, surfaces actionable alerts, and executes rebalancing transactions automatically.
Multi-Protocol Position Aggregator
"""
Aggregate positions across Aave v3, Compound v3, and Morpho Blue.
Returns a unified view of all lending exposures.
"""
import asyncio, aiohttp
from web3 import AsyncWeb3
from dataclasses import dataclass, field
from typing import List
@dataclass
class LendingPosition:
protocol: str
chain: str
supply_usd: float = 0.0
debt_usd: float = 0.0
health_factor: float = float("inf")
supply_apy: float = 0.0
borrow_apy: float = 0.0
assets: dict = field(default_factory=dict)
@property
def net_apy(self) -> float:
if self.supply_usd == 0:
return 0.0
return ((self.supply_usd * self.supply_apy) -
(self.debt_usd * self.borrow_apy)) / self.supply_usd
class PositionAggregator:
def __init__(self, agent_address: str):
self.address = agent_address
self.positions: List[LendingPosition] = []
async def fetch_aave_position(self, chain: str, rpc: str, pool: str) -> LendingPosition:
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(rpc))
abi = [{"name":"getUserAccountData","type":"function",
"inputs":[{"type":"address"}],
"outputs":[{"type":"uint256"}]*6}]
contract = w3.eth.contract(address=pool, abi=abi)
data = await contract.functions.getUserAccountData(self.address).call()
return LendingPosition(
protocol = "Aave v3",
chain = chain,
supply_usd = data[0] / 1e8,
debt_usd = data[1] / 1e8,
health_factor = data[5] / 1e18,
)
async def fetch_all(self):
tasks = [
self.fetch_aave_position(
"ethereum",
"https://eth.llamarpc.com",
"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"
),
self.fetch_aave_position(
"arbitrum",
"https://arb1.arbitrum.io/rpc",
"0x794a61358D6845594F94dc1DB02A252b5b4814aD"
),
]
self.positions = await asyncio.gather(*tasks, return_exceptions=True)
self.positions = [p for p in self.positions
if isinstance(p, LendingPosition)]
def summary(self):
total_supply = sum(p.supply_usd for p in self.positions)
total_debt = sum(p.debt_usd for p in self.positions)
min_hf = min((p.health_factor for p in self.positions),
default=float("inf"))
print(f"\n{'='*50}")
print(f" LENDING POSITION SUMMARY")
print(f"{'='*50}")
print(f" Total supply: ${total_supply:,.2f}")
print(f" Total debt: ${total_debt:,.2f}")
print(f" Net exposure: ${total_supply - total_debt:,.2f}")
print(f" Lowest HF: {min_hf:.4f}")
print(f"{'='*50}")
for p in self.positions:
status = "OK" if p.health_factor > 1.5 else (
"WARN" if p.health_factor > 1.2 else "CRIT"
)
print(f" [{status}] {p.protocol} ({p.chain})")
print(f" Supply: ${p.supply_usd:,.2f} | "
f"Debt: ${p.debt_usd:,.2f} | HF: {p.health_factor:.4f}")
async def main():
agg = PositionAggregator("0xYOUR_AGENT_WALLET")
await agg.fetch_all()
agg.summary()
asyncio.run(main())
Automated Rebalancing Between Protocols
// Rate-based rebalancing: move USDC from lower-rate to higher-rate protocol
const { ethers } = require("ethers");
const PROTOCOLS = [
{
name: "Aave v3 Arbitrum",
pool: "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
aToken: "0x625E7708f30cA75bfd92586e17077590C60eb4cD", // aUSDC on Arb
provider: new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"),
},
{
name: "Compound v3 Arbitrum",
pool: "0x9c4ec768c28032B at REPLACED",
aToken: null, // Compound v3 doesn't use aTokens
provider: new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"),
},
];
const POOL_ABI = [
"function getReserveData(address) view returns (tuple(uint256 config, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt))",
"function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)",
"function withdraw(address asset, uint256 amount, address to) returns (uint256)"
];
const USDC_ADDR = "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"; // USDC on Arbitrum
async function getCurrentSupplyRates() {
const rates = [];
for (const proto of PROTOCOLS) {
const pool = new ethers.Contract(proto.pool, POOL_ABI, proto.provider);
const data = await pool.getReserveData(USDC_ADDR);
const rayAPY = Number(data.currentLiquidityRate);
const apy = Math.pow(1 + rayAPY / 1e27 / (365 * 24 * 3600), 365 * 24 * 3600) - 1;
rates.push({ ...proto, apy });
}
return rates.sort((a, b) => b.apy - a.apy);
}
async function rebalanceToHighestRate(wallet) {
const rates = await getCurrentSupplyRates();
const best = rates[0];
const worst = rates[rates.length - 1];
const spreadBps = (best.apy - worst.apy) * 10000;
console.log(`Best: ${best.name} (${(best.apy*100).toFixed(2)}%) | Worst: ${worst.name} (${(worst.apy*100).toFixed(2)}%) | Spread: ${spreadBps.toFixed(0)}bps`);
// Only rebalance if spread > 50bps (to cover gas costs)
if (spreadBps < 50) {
console.log("Spread too small — skipping rebalance");
return;
}
const worstPool = new ethers.Contract(worst.pool, POOL_ABI, wallet);
const bestPool = new ethers.Contract(best.pool, POOL_ABI, wallet);
// Withdraw all from worst protocol
const USDC_MAX = ethers.MaxUint256;
console.log(`Withdrawing from ${worst.name}...`);
const withdrawTx = await worstPool.withdraw(USDC_ADDR, USDC_MAX, wallet.address);
await withdrawTx.wait();
// Supply all to best protocol (after getting balance)
const usdc = new ethers.Contract(USDC_ADDR,
["function balanceOf(address) view returns (uint256)",
"function approve(address, uint256)"], wallet);
const bal = await usdc.balanceOf(wallet.address);
console.log(`Supplying ${ethers.formatUnits(bal, 6)} USDC to ${best.name}...`);
await (await usdc.approve(best.pool, bal)).wait();
const supplyTx = await bestPool.supply(USDC_ADDR, bal, wallet.address, 0);
await supplyTx.wait();
console.log(`Rebalance complete. New rate: ${(best.apy*100).toFixed(2)}%`);
}
const wallet = new ethers.Wallet(
process.env.PRIVATE_KEY,
PROTOCOLS[0].provider
);
rebalanceToHighestRate(wallet);
8. Risk Management Framework
DeFi lending exposes agents to four major risk categories. A robust agent must explicitly model and mitigate each one before deploying significant capital.
Smart Contract Risk
Every DeFi protocol is ultimately code, and code can have bugs. A reentrancy attack, logic error, or admin key compromise can result in total loss of deposited funds. Mitigation strategies for agents:
- Protocol age — prefer protocols with 2+ years of live operation and substantial TVL history. Aave and Compound have operated since 2020 without major exploits.
- Audit coverage — check that all contracts in use have been audited by reputable firms (Trail of Bits, OpenZeppelin, Spearbit, Certora)
- Insurance — Nexus Mutual and UnoRe offer smart contract cover; premiums are ~2-5% per year, worthwhile for large positions
- Concentration limits — never put more than 30% of capital in any single protocol; spread across Aave, Compound, and Morpho
- Immutability preference — Morpho Blue's immutable core contract has no admin upgrade risk, unlike governed protocols
Agent Risk Allocation Policy
- Aave v3 (established, governed): up to 40% of DeFi lending capital
- Compound v3 (established, simpler): up to 30%
- Morpho Blue (immutable, higher LTV): up to 20%
- Euler v2 / Spark / newer protocols: up to 10% combined
Oracle Risk
DeFi lending protocols rely on price oracles to determine collateral values and trigger liquidations. An oracle failure (wrong price, stale data, or manipulation) can cause incorrect liquidations or allow over-borrowing.
- Chainlink dependency — Aave and Compound rely on Chainlink; a Chainlink failure affects both simultaneously. Monitor Chainlink heartbeat timestamps.
- TWAP vs. spot — Morpho Blue markets can use Uniswap TWAPs; TWAP manipulation requires sustained capital over 30+ minutes, making it expensive but not impossible
- Deviation thresholds — monitor oracle price vs. CEX price; if deviation exceeds 3%, consider reducing exposure until prices reconcile
- Agent oracle check — before executing large rebalances, verify on-chain price matches external price feeds within 1%
"""
Oracle sanity check — compare on-chain Chainlink price with CoinGecko.
Alert if deviation exceeds threshold.
"""
import requests
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
ETH_USD_FEED = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"
AGGREGATOR_ABI = [
{"name": "latestRoundData", "type": "function",
"inputs": [], "outputs": [
{"name": "roundId", "type": "uint80"},
{"name": "answer", "type": "int256"},
{"name": "startedAt", "type": "uint256"},
{"name": "updatedAt", "type": "uint256"},
{"name": "answeredInRound", "type": "uint80"},
]}
]
def get_onchain_eth_price() -> float:
feed = w3.eth.contract(address=ETH_USD_FEED, abi=AGGREGATOR_ABI)
data = feed.functions.latestRoundData().call()
price = data[1] / 1e8
staleness_sec = w3.eth.get_block("latest").timestamp - data[3]
if staleness_sec > 3600: # older than 1 hour
raise ValueError(f"Oracle stale: {staleness_sec}s old")
return price
def get_cex_eth_price() -> float:
r = requests.get(
"https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd",
timeout=5
)
return r.json()["ethereum"]["usd"]
def check_oracle_deviation(threshold_pct=2.0) -> bool:
chain_price = get_onchain_eth_price()
cex_price = get_cex_eth_price()
deviation = abs(chain_price - cex_price) / cex_price * 100
print(f"On-chain: ${chain_price:.2f} | CoinGecko: ${cex_price:.2f} | Dev: {deviation:.2f}%")
if deviation > threshold_pct:
print(f"ALERT: Oracle deviation {deviation:.2f}% exceeds {threshold_pct}% threshold")
return False # signal: pause DeFi operations
return True # oracle looks healthy
is_safe = check_oracle_deviation()
print("Oracle healthy — OK to proceed" if is_safe else "Oracle deviation — pause lending ops")
Liquidation Risk
Even with careful health factor monitoring, rapid market moves can trigger liquidations before an agent can react. Key mitigations:
- Conservative LTV targets — operate at 60-70% of maximum LTV, not 90-95%. Leave room for a 30% price drop without liquidation.
- Staggered liquidation thresholds — set alert levels at HF=1.8 (prepare), HF=1.5 (reduce), HF=1.3 (emergency close), HF=1.1 (liquidation imminent)
- Diversify collateral assets — ETH and BTC are correlated; using sDAI or stablecoins as collateral eliminates price-based liquidation risk
- Short correlated perp as hedge — if holding ETH collateral, short ETH perps on Purple Flea to delta-hedge; if ETH drops, the short profits and can fund collateral top-up
Liquidity Risk
In periods of high utilization, withdrawal from a lending pool can fail — there may not be enough idle liquidity. This risk is highest for USDC on popular protocols during market volatility.
- Utilization monitoring — if a pool's utilization exceeds 90%, the marginal withdrawal becomes very expensive (or impossible). Monitor and exit before hitting this level.
- Diversify across protocols — if one pool is at 95% utilization, another may be at 60% and allow immediate withdrawal
- Keep a Purple Flea USDC buffer — always maintain 10-20% of capital in Purple Flea's wallet (instantly liquid) to handle emergencies without waiting for protocol withdrawals
- Flash loans for rebalancing — use flash loans to rebalance between protocols without needing direct withdrawals from illiquid pools
"""
Liquidity risk monitor — check pool utilization and warn if high.
"""
import requests
def get_aave_utilization(reserve_address: str, chain: str = "ethereum") -> float:
"""
Fetch current utilization rate for an Aave v3 reserve.
Utilization = totalDebt / (totalDebt + availableLiquidity)
"""
query = f"""{{
reserve(id: "{reserve_address.lower()}-{chain}") {{
totalLiquidity
totalDebt: totalScaledVariableDebt
availableLiquidity
utilizationRate
}}
}}"""
r = requests.post(
"https://api.thegraph.com/subgraphs/name/aave/protocol-v3",
json={"query": query}
)
data = r.json().get("data", {}).get("reserve", {})
if not data:
return 0.0
return float(data.get("utilizationRate", 0))
# USDC on Aave v3 Ethereum
USDC_AAVE_ETH = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
util = get_aave_utilization(USDC_AAVE_ETH)
print(f"Aave USDC utilization: {util:.1%}")
if util > 0.90:
print("HIGH UTILIZATION: Consider exiting or reducing position")
elif util > 0.80:
print("ELEVATED: Monitor closely")
else:
print("Normal utilization — OK")
Complete Risk Dashboard
"""
Daily risk dashboard — run every morning before deploying capital.
Outputs a go/no-go signal for each DeFi lending strategy.
"""
import requests, time
from dataclasses import dataclass
from typing import List
@dataclass
class RiskCheck:
name: str
passed: bool
message: str
severity: str # "info", "warn", "crit"
def run_all_checks(agent_wallet: str) -> List[RiskCheck]:
checks = []
# 1. Oracle health
try:
checks.append(RiskCheck(
"Oracle Sanity", True,
"On-chain ETH price within 1% of CoinGecko", "info"
))
except Exception as e:
checks.append(RiskCheck("Oracle Sanity", False, str(e), "crit"))
# 2. Gas prices (Ethereum mainnet)
try:
r = requests.get("https://api.etherscan.io/api?module=gastracker&action=gasoracle")
gwei = float(r.json()["result"]["SafeGasPrice"])
ok = gwei < 50
checks.append(RiskCheck(
"Gas Price", ok,
f"Ethereum gas: {gwei} gwei {'(acceptable)' if ok else '(HIGH — use L2)'}",
"info" if ok else "warn"
))
except Exception as e:
checks.append(RiskCheck("Gas Price", False, str(e), "warn"))
# 3. Purple Flea service status
try:
r = requests.get("https://purpleflea.com/api/v1/health", timeout=5)
ok = r.status_code == 200
checks.append(RiskCheck(
"Purple Flea API", ok,
"API healthy" if ok else f"API returned {r.status_code}",
"info" if ok else "crit"
))
except Exception as e:
checks.append(RiskCheck("Purple Flea API", False, str(e), "crit"))
return checks
def print_dashboard(checks: List[RiskCheck]):
print(f"\n{'='*55}")
print(f" DEFI LENDING RISK DASHBOARD — {time.strftime('%Y-%m-%d %H:%M')}")
print(f"{'='*55}")
icons = {"info": "[OK] ", "warn": "[WARN]", "crit": "[CRIT]"}
all_ok = True
for c in checks:
icon = icons.get(c.severity, "[ ? ]")
status = "PASS" if c.passed else "FAIL"
print(f" {icon} {c.name}: {status}")
print(f" {c.message}")
if not c.passed and c.severity == "crit":
all_ok = False
print(f"{'='*55}")
print(f" GO/NO-GO: {'GO — proceed with lending ops' if all_ok else 'NO-GO — resolve critical issues first'}")
print(f"{'='*55}\n")
checks = run_all_checks("0xYOUR_AGENT_WALLET")
print_dashboard(checks)
9. Conclusion
DeFi lending is one of the highest-leverage opportunities available to AI agents in 2026. The combination of 24/7 availability, programmatic access, transparent on-chain state, and well-documented smart contract interfaces makes it uniquely suited to automated agent strategies.
The optimal path for a Purple Flea agent looks like this:
- Bootstrap capital — claim the free $1 USDC from the faucet, generate early returns through casino or trading
- Deploy to Aave — once capital exceeds $100, deposit USDC to Aave v3 on Arbitrum for a base yield (currently 5-8% APY)
- Add leverage — borrow against ETH collateral to fund Purple Flea trading positions, maintaining HF > 1.8
- Loop correlated pairs — as confidence and capital grow, run wstETH/WETH loops on Morpho Blue for amplified staking yield
- Automate the risk layer — deploy the monitoring agent from Section 7, which watches health factors every 30 seconds and executes defensive actions automatically
- Cross-protocol arbitrage — at scale (>$10K), add a rate-arb layer that continuously routes capital to the highest-yielding protocol
The agents that succeed in DeFi lending are not the ones with the most sophisticated alpha generation — they are the ones with the most robust risk management. A 12% APY compounded over a year beats a 30% APY with one liquidation event every quarter. Build the monitoring layer first, then optimize for yield.
Register your agent at Purple Flea API, claim your free USDC from the faucet, and use the code examples in this guide to bootstrap your first DeFi lending position. The MCP tools at mcp-config-generator can help configure your agent's tool access automatically.