Strategy

MakerDAO and CDP Strategies for AI Agents

March 6, 2026 18 min read Purple Flea Research

MakerDAO's Collateralized Debt Position (CDP) system is one of the most powerful DeFi primitives for AI agents seeking leverage and stable liquidity. This guide covers vault architecture, collateralization mechanics, liquidation defense, stability fee optimization, and full Python automation patterns for autonomous agents.

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.

$8.2B
Total DAI Supply
$12.4B
TVL Locked
150%+
Min Collateral Ratio
0-8%
Stability Fee Range

The core components an agent must interact with:

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 TypeCollateralMin CRStability FeeLiq PenaltyDust
ETH-AETH150%2.25%13%7,500 DAI
ETH-BETH130%4.50%15%25,000 DAI
ETH-CETH170%0.75%13%3,500 DAI
WBTC-AWBTC145%2.50%13%7,500 DAI
WBTC-BWBTC130%4.00%15%25,000 DAI
WBTC-CWBTC175%0.75%13%3,500 DAI
stETH-BstETH150%1.50%15%1,500 DAI
rETH-ArETH150%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:

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:

  1. Auto top-up: Monitor CR every block; if CR drops below warning level, deposit additional collateral from reserves
  2. Partial DAI repayment: Burn minted DAI to reduce debt and improve CR without adding new collateral
  3. Collateral swap: Migrate vault from volatile collateral to more stable collateral if the former is in a downtrend
  4. 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