DEV Community

Ahmed Castro for Filosofía Código EN

Posted on • Edited on

Intro to Programatic Loans in Morpho

From decentralized AI agents to automated DAO‑treasury management and advanced DeFi trading bots, programmable finance has been rising and offers many new possibilities. In this tutorial we’ll learn-by-doing one such use case: taking out a loan by interacting with Morpho v1 to borrow WETH against USDC on Base.

Throughout the guide you’ll learn not only how to deposit USDC and borrow WETH in a single transaction, but also how to repay your debt and retrieve your collateral. You’ll see how to parameterize your calls so that you can switch to any other Morpho market and add your own custom code.

Monolithic vs. Multi‑Market Lending

Major protocols like Aave and Compound adopt a monolithic architecture: a single, unified market pools liquidity across all assets. This yields deep reserves and stability but makes governance changes harder to adapt to the ever changing market conditions.

Monolithic vs. Multi‑Market Lending
Aave offers a single market protocol-wide, while Morpho a multi-market approach.

Morpho instead provides a multi‑market framework. Each whitelisted organizations can create and update new markets, Morpho call this organizations "Curators" and users can select to enter on the market created by the Curators of their choice. Curators manage their own parameters: loan token, collateral token, interest‑rate module (IRM), oracle, collateralization ratio, etc. while Morpho itself supplies the infrastructure and matching engine. The result is flexible markets and more options for users, all accessed via a unified interface.

The Morpho API

Morpho exposes four core functions to manage collateralized loans:

  1. supplyCollateral: Deposit collateral so you can borrow.
  2. borrow: Draw loan assets up to your borrowing power.
  3. repay: Return borrowed principal plus interest.
  4. withdrawCollateral: Retrieve your collateral once you’ve repaid.

For simplicity, our contract will wrap supplyCollateral + borrow into a single borrowAgainstCollateral() call, and repay + withdrawCollateral into repayAndWithdraw().

Market parameters

Because Morpho supports multiple markets, every call requires a MarketParams struct:

struct MarketParams {
    address loanToken;
    address collateralToken;
    address oracle;
    address irm;
    uint256 lltv;
}
Enter fullscreen mode Exit fullscreen mode

In this example we’ll target the WETH/USDC market on Base:

Parameter Description Address / Value
Morpho contract Base deployment 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb
loanToken WETH 0x4200000000000000000000000000000000000006
collateralToken USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
oracle On‑chain price feed 0xD09048c8B568Dbf5f189302beA26c9edABFC4858
irm Adaptive‑Curve IRM 0x46415998764C29aB2a25CbeA6254146D50D22687
lltv 86 % liquidation-to-loan ratio (0.86 × 1e18) 860000000000000000

If you want to target a different market, simply swap out these addresses (all available in Morpho’s documentation and the morpho page.

Morpho Market
You can get your target market from the Morpho app, in our demo we will target this market.

Each Morpho Market Curator can choose their desired collateral and loan token pairs based on market supply and demand. They can also choose their oracle of choice, this is very important because oracles are the least decentralized parts of a lending protocol so Curators can chose the most decentralized one that offers a specific market. In this case is chainlink. And also, depending on market liquidity and volatility they can choose a liquidation threshold, this means the amount of overcolateralization needed to get a loan, also curators can choose the way the liquidation threshold changes depending on market liquidity.

Solidity Implementation

contract demo
Our contract will abstract the borrowing and repayment process into two single transactions.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

// IERC20 interface needed to interact with USDC and WETH
interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}

// Morpho interface to get and repay loans
interface IMorpho{
    struct MarketParams {
        address loanToken;
        address collateralToken;
        address oracle;
        address irm;
        uint256 lltv;
    }

    function supplyCollateral(
        MarketParams calldata marketParams,
        uint256 amount,
        address onBehalf,
        bytes calldata data
    ) external;

    function borrow(
        MarketParams calldata marketParams,
        uint256 amount,
        uint256 maxShares,
        address onBehalf,
        address receiver
    ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);

    function repay(
        MarketParams calldata marketParams,
        uint256 amount,
        uint256 maxShares,
        address onBehalf,
        bytes calldata data
    ) external returns (uint256 assetsRepaid, uint256 sharesRepaid);

    function withdrawCollateral(
        MarketParams calldata marketParams,
        uint256 amount,
        address onBehalf,
        address receiver
    ) external;
}

// Demo contrat that abstracts borrowing and repayment into two functions
contract MorphoBorrower is Ownable {
    address public constant morphoAddress = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;     // Morpho address on Base

    address public constant loanToken = 0x4200000000000000000000000000000000000006;         // WETH on base
    address public constant collateralToken = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;   // USDC on base
    address public constant oracle = 0xD09048c8B568Dbf5f189302beA26c9edABFC4858;            // Chainlink oracle for our market
    address public constant irm = 0x46415998764C29aB2a25CbeA6254146D50D22687;               // Adaptative curve strategy for our market
    uint256 public constant lltv = 860000000000000000;                                      // 86% loan to liquidation treshold

    constructor() Ownable(msg.sender) {}

    // Our target morpho market settings
    IMorpho.MarketParams public marketParams = IMorpho.MarketParams({
        loanToken: loanToken,
        collateralToken: collateralToken,
        oracle: oracle,
        irm: irm,
        lltv: lltv
    });

    uint256 public sharesBorrowed; // Asset borrow tracking

    // Borrow WETH against USDC
    function borrowAgainstCollateral(uint256 collateralAmount, uint256 borrowAmount) external onlyOwner {
        // Transfer USDC from user and supply it as collateral in the Morpho market
        IERC20(collateralToken).transferFrom(msg.sender, address(this), collateralAmount);
        IERC20(collateralToken).approve(morphoAddress, collateralAmount);
        IMorpho(morphoAddress).supplyCollateral(
            marketParams,
            collateralAmount,
            address(this),
            ""
        );

        // Open a borrow position on this smart contract account and transfer the borrowed WETH to the user
        (uint256 assetsBorrowed, uint256 shares) = IMorpho(morphoAddress).borrow(
            marketParams,
            borrowAmount,
            0,
            address(this),
            address(this)
        );
        sharesBorrowed = shares;
        IERC20(loanToken).transfer(msg.sender, assetsBorrowed);
    }

    function repayAndWithdraw(uint256 repayAmount, uint256 collateralAmount) external onlyOwner {
        // Transfer WETH from the user and repay the debt with it
        IERC20(loanToken).transferFrom(msg.sender, address(this), repayAmount);
        IERC20(loanToken).approve(morphoAddress, repayAmount);
        IMorpho(morphoAddress).repay(
            marketParams,
            0,
            sharesBorrowed,
            address(this),
            ""
        );

        // Transfer back excess loanToken
        IERC20(loanToken).transfer(msg.sender, IERC20(loanToken).balanceOf(address(this)));

        // Withdraw all collateral and send to sender
        IMorpho(morphoAddress).withdrawCollateral(
            marketParams,
            collateralAmount,
            address(this),
            msg.sender
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

How to Use

On the BaseScan's USDC page, call approve(spender, amount) by setting your MorphoBorrower address as spender and e.g. 3000 (which equals 0.003 USDC, since USDC has 6 decimals).

Approve on basescan
Approve your contract from BaseScan's USDC page.

Then, borrow ETH by calling borrowAgainstCollateral(3000, 1_000_000_000) that is, 0.003 USDC collateral to borrow 0.000000001 WETH. At this point the WETH will be in your wallet and you are free to use it.

Morpho get loan
Try getting a 0.000000001 WETH with a 0.0003 collateral. Explore an on-chain example here.

To repay your debt, approve WETH on BaseScan using enough to cover principal + interest (e.g. 1_010_000_000). Then call repayAndWithdraw(1_010_000_000, 3000). Any excess WETH will automatically be refunded, and you’ll receive back your 0.003 USDC.

Morpho repay debt
When the debt is repayed, notice how all excess WETH is sent back to us. Explore the on-chain transaction here.

Congratulations! You’ve now interacted with Morpho v1 to take out and repay a loan. From here you can retarget any other Morpho market by swapping out the addresses in MarketParams, or integrate these functions into your own dApp, DAO workflow or AI agent for fully programmable lending strategies.

Top comments (1)

Collapse
 
patron profile image
Austin

Heyy Cool blog! Many thanks.
Could you please uncover contango_xyz use cases? Thanks in advance :)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.