1. MakerDAO Architecture
MakerDAO is a decentralized protocol on Ethereum that enables users and agents to lock collateral assets into smart contracts — called Vaults (formerly CDPs) — and mint DAI, a decentralized stablecoin soft-pegged to the US dollar. The system is governed by MKR token holders who set risk parameters via on-chain governance.
For AI agents, MakerDAO is particularly valuable because it provides on-demand stable liquidity without counterparty risk. An agent can lock ETH, generate DAI, deploy that DAI across yield strategies, and repay the debt algorithmically — creating a leverage loop or liquidity bridge entirely in code.
The core components an agent must interact with:
- CDP Manager: creates and tracks individual vault positions
- Vat: the core accounting engine for all collateral and debt
- Spot: price feed oracle that determines collateral value
- Dog: liquidation engine (Liquidation 2.0)
- Jug: stability fee accumulator
- DaiJoin: converts internal DAI units to ERC-20 DAI
The Agent Workflow
A typical agent CDP workflow: deposit collateral → open vault → mint DAI → deploy DAI for yield → collect yield → repay DAI + stability fee → reclaim collateral. The loop repeats based on market conditions and risk parameters set by the agent's strategy configuration.
2. Vault Types and Parameters
MakerDAO supports multiple vault types, each with distinct risk parameters. Choosing the right vault is critical for agents optimizing between cost (stability fee), safety (liquidation ratio), and capital efficiency (debt ceiling).
| Vault Type | Collateral | Min CR | Stability Fee | Liq Penalty | Dust |
|---|---|---|---|---|---|
| ETH-A | ETH | 150% | 2.25% | 13% | 7,500 DAI |
| ETH-B | ETH | 130% | 4.50% | 15% | 25,000 DAI |
| ETH-C | ETH | 170% | 0.75% | 13% | 3,500 DAI |
| WBTC-A | WBTC | 145% | 2.50% | 13% | 7,500 DAI |
| WBTC-B | WBTC | 130% | 4.00% | 15% | 25,000 DAI |
| WBTC-C | WBTC | 175% | 0.75% | 13% | 3,500 DAI |
| stETH-B | stETH | 150% | 1.50% | 15% | 1,500 DAI |
| rETH-A | rETH | 150% | 0.75% | 13% | 1,500 DAI |
Agent Tip: ETH-C is optimal for long-term hold strategies — the lowest stability fee (0.75%) rewards patient agents. ETH-B suits short-term leverage where the tighter 130% liquidation ratio allows more capital extraction at the cost of higher fees.
Vault selection should be programmatically evaluated based on current yield spreads. If you can deploy DAI at 6% APY and the stability fee is 0.75%, you net 5.25% annually on borrowed funds — a strong carry trade.
3. Collateralization Ratio Mechanics
The collateralization ratio (CR) is the single most important parameter an agent monitors. It determines both safety and capital efficiency. The CR is calculated as:
CR = (Collateral Value in USD) / (Outstanding DAI Debt) × 100%
Example:
Collateral: 10 ETH @ $3,200 = $32,000
DAI Minted: 18,000 DAI
CR = ($32,000 / $18,000) × 100% = 177.8%
Agents should define three CR thresholds in their configuration:
- Target CR: Where the agent aims to operate (e.g., 200% for ETH-A)
- Warning CR: Triggers rebalancing (e.g., 165%)
- Emergency CR: Triggers aggressive action (e.g., 155%) — above liquidation at 150% but with minimal margin
Dynamic CR Targets Based on Volatility
Sophisticated agents adjust CR targets based on realized and implied volatility of the collateral asset. In high-volatility regimes, maintain higher CRs. In calm markets, compress toward target efficiency ratios.
import numpy as np
def compute_dynamic_cr_target(
base_cr: float,
historical_returns: list[float],
vol_lookback: int = 30
) -> float:
"""Adjust CR target based on recent volatility."""
daily_vol = np.std(historical_returns[-vol_lookback:])
annualized_vol = daily_vol * np.sqrt(365)
# Scale CR: +10% for each 20% of annualized vol above 40%
vol_adj = max(0, (annualized_vol - 0.4) / 0.2) * 10
return base_cr + vol_adj
4. Stability Fee Optimization
Stability fees are the interest rate agents pay on minted DAI. They accrue continuously and compound over time. Governance can change these rates, so agents must monitor on-chain proposals via the MakerDAO governance API or the DSChief contract.
The net yield an agent captures from a CDP strategy is:
Net Yield = DAI Deployment APY - Stability Fee - Gas Costs (annualized)
Example comparison:
ETH-A: 8% - 2.25% - 0.5% = 5.25% net
ETH-B: 8% - 4.50% - 0.5% = 3.0% net
ETH-C: 8% - 0.75% - 0.5% = 6.75% net (with 170% min CR constraint)
Monitoring Governance for Rate Changes
Stability fees are set by Maker governance through weekly "Rates Proposals." An agent that can predict or react quickly to rate changes gains a competitive edge. Monitor the DSPause contract for queued executive spells and the Jug contract for current rates.
from web3 import Web3
import json
JUG_ADDRESS = "0x19c0976f590D67707E62397C87829d896Dc0f1F"
JUG_ABI = json.loads('[{"inputs":[{"name":"","type":"bytes32"}],"name":"ilks","outputs":[{"name":"duty","type":"uint256"},{"name":"rho","type":"uint256"}],"type":"function"}]')
def get_stability_fee(w3: Web3, ilk_name: str) -> float:
"""Get annualized stability fee for a vault type."""
jug = w3.eth.contract(address=JUG_ADDRESS, abi=JUG_ABI)
ilk_bytes = ilk_name.encode().ljust(32, b'\x00')
duty, _ = jug.functions.ilks(ilk_bytes).call()
# duty is in RAY (10^27 per second), convert to annual %
per_second = duty / 10**27
annual = (per_second ** (365 * 24 * 3600) - 1) * 100
return round(annual, 4)
5. DAI Minting Process
Minting DAI involves three on-chain steps: approving the collateral transfer, locking collateral in the vault (gem join), and drawing DAI from the vault (exit from DaiJoin). Agents should batch these into a single multicall transaction to minimize gas costs.
from web3 import Web3
from eth_account import Account
import json, os
PROXY_REGISTRY = "0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE"
class MakerVaultManager:
def __init__(self, w3: Web3, private_key: str):
self.w3 = w3
self.account = Account.from_key(private_key)
self.proxy = self._get_or_create_proxy()
def _get_or_create_proxy(self):
"""Get DS Proxy (required for Maker interactions)."""
registry = self.w3.eth.contract(
address=PROXY_REGISTRY,
abi=PROXY_REGISTRY_ABI
)
proxy = registry.functions.proxies(self.account.address).call()
if proxy == '0x0000000000000000000000000000000000000000':
tx = registry.functions.build().build_transaction({
'from': self.account.address,
'gas': 500000,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
signed = self.account.sign_transaction(tx)
self.w3.eth.send_raw_transaction(signed.rawTransaction)
return proxy
def open_and_draw(
self,
ilk: str,
collateral_amount: int, # in wei
dai_to_mint: int # in WAD (10^18)
) -> str:
"""Open vault, deposit collateral, and mint DAI."""
# Use DssProxyActions for atomic open+lock+draw
actions = self.w3.eth.contract(
address=DSS_PROXY_ACTIONS,
abi=PROXY_ACTIONS_ABI
)
calldata = actions.encodeABI(
fn_name='openLockETHAndDraw',
args=[CDP_MANAGER, JUG, ETH_JOIN, DAI_JOIN,
ilk.encode().ljust(32, b'\x00'), dai_to_mint]
)
proxy_contract = self.w3.eth.contract(
address=self.proxy,
abi=DS_PROXY_ABI
)
tx = proxy_contract.functions.execute(
DSS_PROXY_ACTIONS, calldata
).build_transaction({
'from': self.account.address,
'value': collateral_amount,
'gas': 400000,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
signed = self.account.sign_transaction(tx)
tx_hash = self.w3.eth.send_raw_transaction(signed.rawTransaction)
return tx_hash.hex()
6. Liquidation 2.0 Mechanics
Maker's Liquidation 2.0 system replaced the old auction model with a Dutch auction mechanism called Clip. When a vault's CR falls below the minimum, any keeper can trigger liquidation. The collateral is then sold via a decreasing-price Dutch auction, protecting vaults from flash crash cascades.
Warning: A liquidated vault incurs both the liquidation penalty (13-15% depending on vault type) AND the stability fee accrued to that point. An agent must never allow positions to approach the liquidation threshold.
Liquidation Defense Strategies
Defense strategies agents use to avoid liquidation:
- Auto top-up: Monitor CR every block; if CR drops below warning level, deposit additional collateral from reserves
- Partial DAI repayment: Burn minted DAI to reduce debt and improve CR without adding new collateral
- Collateral swap: Migrate vault from volatile collateral to more stable collateral if the former is in a downtrend
- CR buffer sizing: Set target CR proportional to expected maximum drawdown of the collateral asset
import asyncio
from web3 import AsyncWeb3
async def monitor_vault_health(
vault_manager: MakerVaultManager,
vault_id: int,
warning_cr: float = 165.0,
emergency_cr: float = 155.0,
poll_interval: int = 12 # seconds (1 block)
):
"""Continuously monitor vault health and react to CR changes."""
while True:
cr = await vault_manager.get_collateralization_ratio(vault_id)
if cr <= emergency_cr:
# Emergency: repay DAI to restore safety immediately
repay_amount = await vault_manager.calc_repay_to_target(
vault_id, target_cr=185.0
)
await vault_manager.wipe_dai(vault_id, repay_amount)
await vault_manager.log_action(f"EMERGENCY repay {repay_amount} DAI")
elif cr <= warning_cr:
# Warning: add collateral from reserves
top_up = await vault_manager.calc_collateral_to_target(
vault_id, target_cr=185.0
)
if top_up > 0:
await vault_manager.lock_eth(vault_id, top_up)
await vault_manager.log_action(f"Top-up {top_up/1e18:.4f} ETH")
await asyncio.sleep(poll_interval)
7. Vault Optimization Strategies
Beyond basic vault management, agents can implement several advanced optimization strategies to maximize returns and minimize costs.
Multi-Vault Distribution
Instead of a single vault, agents can spread positions across multiple vault types to optimize the stability fee vs. liquidation ratio trade-off. For example: 60% of capital in ETH-C (lowest fee, highest CR) and 40% in ETH-A (moderate fee, moderate CR) provides both cost efficiency and flexibility.
Stability Fee Arbitrage
When governance queues a stability fee increase, migrate vault balances from the about-to-increase type to alternatives before the change takes effect. The DSPause contract has a minimum delay (typically 48 hours) which gives agents time to reposition.
DAI Savings Rate Integration
The DAI Savings Rate (DSR) allows agents to earn yield on idle DAI. When DAI deployment opportunities dry up, park minted DAI in the DSR to offset part of the stability fee:
class DSRManager:
"""Interact with MakerDAO DAI Savings Rate."""
def __init__(self, w3: Web3, pot_address: str):
self.pot = w3.eth.contract(address=pot_address, abi=POT_ABI)
def get_dsr(self) -> float:
"""Get current DSR as annualized percentage."""
dsr = self.pot.functions.dsr().call()
per_second = dsr / 10**27
return (per_second ** (365 * 24 * 3600) - 1) * 100
def should_use_dsr(self, stability_fee: float) -> bool:
"""Determine if DSR is worth using vs. repaying debt."""
dsr = self.get_dsr()
gas_cost_annual = 0.2 # estimated % of position
return dsr > gas_cost_annual # use DSR if yield exceeds costs
8. Python MakerAgent with Health Monitoring
The following is a complete, production-oriented MakerAgent class that handles vault lifecycle, health monitoring, auto-top-up, and DAI deployment coordination with external yield sources.
import asyncio
import logging
from dataclasses import dataclass
from typing import Optional
from web3 import AsyncWeb3
from decimal import Decimal
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('MakerAgent')
@dataclass
class VaultConfig:
ilk: str # e.g. "ETH-C"
target_cr: float = 200.0
warning_cr: float = 170.0
emergency_cr: float = 155.0
max_dai_utilization: float = 0.9 # fraction of mintable DAI to use
rebalance_threshold: float = 5.0 # % deviation to trigger rebalance
class MakerAgent:
"""Full-lifecycle MakerDAO vault management agent."""
def __init__(
self,
w3: AsyncWeb3,
private_key: str,
config: VaultConfig,
yield_strategy: Optional[callable] = None
):
self.w3 = w3
self.config = config
self.yield_strategy = yield_strategy
self.vault_id: Optional[int] = None
self.total_dai_minted = Decimal(0)
self.total_yield_earned = Decimal(0)
async def initialize(self, initial_eth: int) -> int:
"""Open vault with initial ETH collateral."""
cr = self.config.target_cr / 100
eth_price = await self._get_eth_price()
max_dai = int((initial_eth * eth_price) / (cr * 1e18) * 1e18)
dai_to_mint = int(max_dai * self.config.max_dai_utilization)
logger.info(f"Opening {self.config.ilk} vault: {initial_eth/1e18:.4f} ETH → {dai_to_mint/1e18:.2f} DAI")
self.vault_id = await self._open_vault(initial_eth, dai_to_mint)
self.total_dai_minted = Decimal(dai_to_mint) / Decimal(10**18)
return self.vault_id
async def run(self, duration_hours: int = 24):
"""Main agent loop."""
end_time = asyncio.get_event_loop().time() + duration_hours * 3600
while asyncio.get_event_loop().time() < end_time:
try:
state = await self._get_vault_state()
await self._handle_health(state)
await self._optimize_yield(state)
await self._log_state(state)
except Exception as e:
logger.error(f"Loop error: {e}")
await asyncio.sleep(30) # check every 30s (~2.5 blocks)
async def _handle_health(self, state: dict):
cr = state['cr']
if cr <= self.config.emergency_cr:
repay = await self._calc_dai_repay(state, self.config.target_cr + 20)
await self._repay_dai(repay)
logger.warning(f"EMERGENCY repay: {repay/1e18:.2f} DAI (CR was {cr:.1f}%)")
elif cr <= self.config.warning_cr:
top_up = await self._calc_eth_top_up(state, self.config.target_cr)
await self._add_collateral(top_up)
logger.info(f"Top-up: {top_up/1e18:.4f} ETH (CR was {cr:.1f}%)")
elif cr > self.config.target_cr + self.config.rebalance_threshold * 2:
# Vault is over-collateralized; mint more DAI if profitable
extra = await self._calc_mintable_at_target(state)
if extra > 0:
await self._draw_dai(extra)
logger.info(f"Drew extra {extra/1e18:.2f} DAI (CR {cr:.1f}% above target)")
async def _optimize_yield(self, state: dict):
"""Deploy DAI balance to highest available yield."""
if self.yield_strategy:
dai_balance = await self._get_dai_balance()
if dai_balance > 100e18: # min 100 DAI threshold
yield_earned = await self.yield_strategy(dai_balance)
self.total_yield_earned += Decimal(yield_earned) / Decimal(1e18)
async def close_vault(self):
"""Unwind position: repay all DAI, reclaim ETH."""
state = await self._get_vault_state()
await self._repay_dai(state['debt'])
collateral = await self._free_collateral(self.vault_id)
logger.info(f"Vault closed. Reclaimed {collateral/1e18:.4f} ETH. Net yield: {self.total_yield_earned:.2f} DAI")
return collateral
9. DAI Deployment Strategies
Minting DAI is only the first step. The real alpha comes from deploying that DAI productively. Here are the strategies agents use, ranked by risk-adjusted return:
Strategy 1: Aave/Compound Lending
Supply DAI to Aave v3 or Compound v3. Current yields: 4-8% APY depending on utilization. Low risk, deep liquidity, easily automated. Ideal base strategy for conservative agents.
Strategy 2: Curve 3Pool / FRAX LP
Provide liquidity to Curve's 3pool (DAI/USDC/USDT) or FRAX-based pools. Earn swap fees (0.04% per trade) plus CRV emissions. Yields typically 3-6% base + CRV rewards on top. Requires managing Curve gauge votes for optimal emissions.
Strategy 3: Convex Finance
Stake Curve LP tokens in Convex to earn boosted CRV + CVX rewards without locking CRV. Effectively automates the Curve yield optimization. Current yields for DAI pools: 6-12% APY including token rewards.
Strategy 4: Morpho Optimizer
Morpho sits between lenders and borrowers, matching them peer-to-peer for improved rates. DAI suppliers can earn above Aave/Compound rates while maintaining the same liquidity profile.
from enum import Enum
from dataclasses import dataclass
class YieldStrategy(Enum):
AAVE = "aave"
COMPOUND = "compound"
CURVE_3POOL = "curve_3pool"
CONVEX = "convex"
MORPHO = "morpho"
DSR = "dsr"
async def select_best_strategy(
yield_fetcher,
min_yield: float = 3.0, # minimum acceptable APY %
max_risk_score: int = 5 # 1-10 scale
) -> YieldStrategy:
"""Dynamically select optimal DAI deployment strategy."""
yields = {
YieldStrategy.AAVE: (await yield_fetcher.aave_dai_apy(), 2),
YieldStrategy.COMPOUND: (await yield_fetcher.compound_dai_apy(), 2),
YieldStrategy.CURVE_3POOL: (await yield_fetcher.curve_3pool_apy(), 3),
YieldStrategy.CONVEX: (await yield_fetcher.convex_dai_apy(), 4),
YieldStrategy.MORPHO: (await yield_fetcher.morpho_dai_apy(), 3),
YieldStrategy.DSR: (await yield_fetcher.dsr_apy(), 1),
}
eligible = {
k: v[0] for k, v in yields.items()
if v[0] >= min_yield and v[1] <= max_risk_score
}
return max(eligible, key=eligible.get) if eligible else YieldStrategy.DSR
10. Risk Management
CDP strategies carry multiple correlated risks that agents must quantify and manage:
Oracle Risk
MakerDAO's OSM (Oracle Security Module) delays price updates by one hour. In a flash crash, the delayed oracle may overstate collateral value, giving an agent false safety before the price snaps in an hour. Agents should use external price feeds (Chainlink, Pyth) in parallel and react to those rather than waiting for the OSM.
Smart Contract Risk
MakerDAO is battle-tested but not immutable. Keep vault positions within comfortable bounds. Diversify across protocols. Maintain a DAI reserve outside the vault to handle emergency repayments without DEX slippage under stress.
Critical: Never run a vault position that would require selling collateral at market to avoid liquidation. Always maintain a DAI reserve buffer equal to at least 10% of outstanding debt for emergency repayment.
Governance Risk
Maker governance can change stability fees, liquidation ratios, or even add new collateral types that affect system-wide DAI demand. Monitor the Maker governance forum, snapshot votes, and on-chain executive spells as part of your risk feed.
Build Your Agent on Purple Flea
Purple Flea provides financial infrastructure for AI agents — casino games, perpetual futures, wallets, domain services, faucet, and escrow. Start with free test funds from our faucet, then scale to real strategies.
Explore Purple Flea