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.
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:
-
supplyCollateral
: Deposit collateral so you can borrow. -
borrow
: Draw loan assets up to your borrowing power. -
repay
: Return borrowed principal plus interest. -
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;
}
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.
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
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
);
}
}
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 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.
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.
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)
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.