Supply Collateral, Borrow, Repay & Withdraw Collateral
This tutorial provides a comprehensive guide for developers on how to integrate the core functionalities of Morpho Markets: supplying collateral, borrowing, repaying debt, and withdrawing collateral.
Unlike ERC4626 vaults, Morpho Markets have a unique interface defined by the core Morpho
contract. Understanding these functions is essential for building any application with borrowing capabilities.
We will cover three primary methods of interaction:
- Direct Smart Contract Integration using Solidity.
- Offchain Integration using the Morpho SDK with TypeScript and Viem.
- Advanced Integration using Morpho Bundlers for atomic, multi-step actions.
Key Concepts: Assets vs. Shares
The core accounting system in Morpho Markets revolves around the concepts of assets
and shares
. This applies to the borrow
positions (the loanable asset), but not to the collateral.
Concept | Functions Affected | Description |
---|---|---|
Assets | borrow , repay | The actual underlying token (e.g., USDC, WETH) that a user wants to lend or borrow. |
Shares | borrow , repay | Internal accounting units representing a proportional claim on the market's total supply or debt. |
- For
borrow
, using theassets
parameter is the most common and intuitive approach for users. - For full repayments, using the
shares
parameter is highly recommended. Repaying a user's exactborrowShares
balance ensures the debt is fully cleared, avoiding "dust" amounts left over from rounding. - Collateral (
supplyCollateral
andwithdrawCollateral
) is always handled inassets
.
Prerequisites
Before you begin, you will need:
- The
MarketParams
for the market you want to interact with. You can find active markets using the Morpho API. - An account with a balance of the collateral asset (e.g., wstETH) and the loan asset (e.g., WETH for repayments).
Method 1: Smart Contract Integration (Solidity)
This method is for developers building smart contracts that interact directly with Morpho Markets. The typical flow is: supply collateral, borrow, repay, and then withdraw collateral.
Step 1: Supply Collateral
First, the user must supply a collateral asset to the market. This requires approving the Morpho contract to spend the collateral token.
import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Within your contract
IMorpho morpho = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb);
MarketParams memory marketParams = ...; // The parameters for the chosen market
uint256 collateralAmount = 1 ether;
// 1. Approve Morpho to spend the collateral token
IERC20(marketParams.collateralToken).approve(address(morpho), collateralAmount);
// 2. Supply the collateral
morpho.supplyCollateral(marketParams, collateralAmount, msg.sender, "");
Step 2: Borrow Assets
Once collateral is supplied, the user can borrow the loan asset against it.
// Borrowing a specific amount of the loan token (asset-first approach)
uint256 borrowAmount = 1000 * 10**18; // Example: 1000 WETH
// The final two arguments are onBehalf and receiver
(uint256 assetsBorrowed, ) = morpho.borrow(marketParams, borrowAmount, 0, msg.sender, msg.sender);
Step 3: Repay Debt
To repay the loan, the user must approve the Morpho contract to spend the loan token.
// Asset-first approach: Repay a specific amount of the loan token.
uint256 repayAmount = 500 * 10**18;
IERC20(marketParams.loanToken).approve(address(morpho), repayAmount);
morpho.repay(marketParams, repayAmount, 0, msg.sender, "");
Step 4: Withdraw Collateral
After the debt is fully or partially repaid, the user can withdraw their collateral, provided their position remains healthy.
uint256 amountToWithdraw = 0.5 ether;
// The final two arguments are onBehalf and receiver
morpho.withdrawCollateral(marketParams, amountToWithdraw, msg.sender, msg.sender);
Full Example: Solidity Snippet
Here is a complete example contract demonstrating the full borrow-repay cycle.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IMorpho, MarketParams, Market, Position } from "morpho-blue/src/interfaces/IMorpho.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MorphoBalancesLib } from "morpho-blue/src/libraries/periphery/MorphoBalancesLib.sol";
import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol";
contract MorphoMarketInteraction {
using MarketParamsLib for MarketParams;
using MorphoBalancesLib for IMorpho;
IMorpho public immutable morpho;
MarketParams public immutable marketParams;
constructor(IMorpho _morpho, MarketParams memory _marketParams) {
morpho = _morpho;
marketParams = _marketParams;
}
function supplyCollateralAndBorrow(uint256 collateralAmount, uint256 borrowAmount) external {
IERC20(marketParams.collateralToken).approve(address(morpho), collateralAmount);
morpho.supplyCollateral(marketParams, collateralAmount, msg.sender, "");
morpho.borrow(marketParams, borrowAmount, 0, msg.sender, msg.sender);
}
function repayAllAndWithdraw(uint256 collateralAmount) external {
uint256 debt = morpho.expectedBorrowAssets(marketParams, msg.sender);
IERC20(marketParams.loanToken).approve(address(morpho), debt);
(, uint128 borrowShares, ) = morpho.position(marketParams.id(), msg.sender);
morpho.repay(marketParams, 0, borrowShares, msg.sender, "");
morpho.withdrawCollateral(marketParams, collateralAmount, msg.sender, msg.sender);
}
}
Method 2: Offchain Integration (TypeScript SDK)
This method is ideal for dApp frontends or backend services. We'll use TypeScript with Viem.
Step 1: Setup
Install dependencies and set up your Viem client.
npm install viem @morpho-org/blue-sdk @morpho-org/morpho-ts
import { createWalletClient, http, publicActions, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
import { IMorpho_ABI } from "@morpho-org/morpho-ts";
const morphoAddress = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb";
const marketParams = { ... }; // Your target market's parameters
const account = privateKeyToAccount("0x...");
const client = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL_MAINNET),
}).extend(publicActions);
Step 2: Supply Collateral & Borrow
This example combines supplying collateral and borrowing. In a real app, these would likely be separate user actions.
const collateralAmount = parseUnits("1.0", 18); // 1 wstETH
const borrowAmount = parseUnits("2000", 18); // 2000 WETH
// 1. Approve collateral
await client.writeContract({
address: marketParams.collateralToken,
abi: IERC20_ABI, // A standard ERC20 ABI
functionName: "approve",
args: [morphoAddress, collateralAmount],
});
// 2. Supply collateral
await client.writeContract({
address: morphoAddress,
abi: IMorpho_ABI,
functionName: "supplyCollateral",
args: [marketParams, collateralAmount, account.address, "0x"],
});
// 3. Borrow
await client.writeContract({
address: morphoAddress,
abi: IMorpho_ABI,
functionName: "borrow",
args: [marketParams, borrowAmount, 0, account.address, account.address],
});
Step 3: Repay & Withdraw
This example shows a full repayment using the shares
parameter.
// 1. Get user's borrow shares
const { borrowShares } = await client.readContract({
address: morphoAddress,
abi: IMorpho_ABI,
functionName: "position",
args: [marketId, account.address],
});
if (borrowShares > 0) {
// 2. Approve repayment (for the full debt amount in assets)
// The Morpho SDK can help calculate the expected asset amount for the shares.
const expectedDebtAssets = ...;
await client.writeContract({
address: marketParams.loanToken,
abi: IERC20_ABI,
functionName: "approve",
args: [morphoAddress, expectedDebtAssets],
});
// 3. Repay full debt using shares
await client.writeContract({
address: morphoAddress,
abi: IMorpho_ABI,
functionName: "repay",
args: [marketParams, 0, borrowShares, account.address, "0x"],
});
// 4. Withdraw collateral
const collateralToWithdraw = parseUnits("1.0", 18);
await client.writeContract({
address: morphoAddress,
abi: IMorpho_ABI,
functionName: "withdrawCollateral",
args: [marketParams, collateralToWithdraw, account.address, account.address],
});
}
Method 3: Advanced Integration with Bundlers
For the best user experience, especially for actions that require multiple steps (like supply and borrow), Morpho Bundlers are the recommended solution.
The bundler abstracts away the complexity. You define your intent, and it creates a single, atomic transaction.
For example, to supply collateral and borrow in one click:
import { type InputBundlerOperation } from "@morpho-org/bundler-sdk-viem";
const supplyCollateralOp: InputBundlerOperation = {
type: "Blue_SupplyCollateral",
args: { /* ... */ },
};
const borrowOp: InputBundlerOperation = {
type: "Blue_Borrow",
args: { /* ... */ },
};
// The bundler SDK would take [supplyCollateralOp, borrowOp] and create one transaction
// that automatically includes the necessary `approve` call.