Links

Track borrow positions

Learn how to track any position borrowed through Morpho, on-chain via a Smart Contract & off-chain via ethers.js.
Morpho's Lens exposes generic information about the protocol, such as the total borrows in USD (18 decimals). Anyone can query it off-chain through Etherscan or with ethers.js, or on-chain using a Smart Contract.
In addition to querying generic data, anyone can, at anytime, query Morpho's Lens to get information about anyone's borrow position. Here are concrete examples 👇

Morpho-Compound

Solidity
ethers.js
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.16;
import {ILens} from "@morpho-dao/morpho-core-v1/contracts/compound/interfaces/ILens.sol";
import {IMorpho, ICompoundOracle} from "@morpho-dao/morpho-core-v1/contracts/compound/interfaces/IMorpho.sol";
import {CompoundMath} from "@morpho-dao/morpho-utils/src/math/CompoundMath.sol";
contract MorphoCompoundBorrower {
using CompoundMath for uint256;
address public constant CDAI = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643;
address public constant CWBTC = 0xC11b1268C1A384e55C48c2391d8d480264A3A7F4;
address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67;
address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888;
ICompoundOracle public immutable ORACLE;
constructor() {
ORACLE = ICompoundOracle(IMorpho(MORPHO).comptroller().oracle());
}
/// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-Compound.
/// @return borrowedOnPool The amount of WBTC borrowed on Compound's pool (with 8 decimals, the number of decimals of WBTC).
/// @return borrowedP2P The amount of WBTC borrowed peer-to-peer through Morpho-Compound (with 8 decimals, the number of decimals of WBTC).
function getWBTCBorrowBalance()
public
view
returns (uint256 borrowedOnPool, uint256 borrowedP2P)
{
(borrowedOnPool, borrowedP2P, ) = ILens(LENS)
.getCurrentBorrowBalanceInOf(
CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
address(this) // the address of the user you want to know the borrow of
);
}
/// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-Compound.
/// @return borrowedOnPoolUSD The USD value of the amount of WBTC borrowed on Compound's pool (with 18 decimals, whatever the market).
/// @return borrowedP2PUSD The USD value of the amount of WBTC borrowed peer-to-peer through Morpho-Compound (with 18 decimals, whatever the market).
function getWBTCBorrowBalanceUSD()
public
view
returns (uint256 borrowedOnPoolUSD, uint256 borrowedP2PUSD)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 oraclePrice = ORACLE.getUnderlyingPrice(CWBTC2); // with (36 - nb decimals of WBTC = 30) decimals
borrowedOnPoolUSD = borrowedOnPool.mul(oraclePrice); // with 18 decimals, whatever the underlying token
borrowedP2PUSD = borrowedP2P.mul(oraclePrice); // with 18 decimals, whatever the underlying token
}
/// @notice Returns the average borrow rate per block experienced on the DAI market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
/// @return The rate per block at which borrow interests are accumulated on average on the DAI market (with 18 decimals, whatever the market).
function getDAIAvgBorrowRatePerBlock() public view returns (uint256) {
return
ILens(LENS).getAverageBorrowRatePerBlock(
CDAI // the DAI market, represented by the cDAI ERC20 token
);
}
/// @notice Returns the average borrow APR experienced on the DAI market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
/// @return The APR at which borrow interests are accumulated on average on the DAI market (with 18 decimals, whatever the market).
function getDAIAvgBorrowAPR() public view returns (uint256) {
return getDAIAvgBorrowRatePerBlock() * BLOCKS_PER_YEAR;
}
/// @notice Returns the borrow rate per block this contract experiences on the WBTC market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
/// @return The rate per block at which borrow interests are accrued by this contract on the WBTC market (with 18 decimals).
function getWBTCBorrowRatePerBlock() public view returns (uint256) {
return
ILens(LENS).getCurrentUserBorrowRatePerBlock(
CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
address(this) // the address of the user you want to know the borrow rate of
);
}
/// @notice Returns the expected APR at which borrow interests are accrued by this contract, on the WBTC market.
/// @return The APR at which WBTC borrow interests are accrued (with 18 decimals, whatever the market).
function getWBTCBorrowAPR() public view returns (uint256) {
uint256 borrowRatePerBlock = getWBTCBorrowRatePerBlock();
return borrowRatePerBlock * BLOCKS_PER_YEAR;
}
/// @notice Returns the borrow APR this contract will experience (at maximum) if it borrows the given amount from the WBTC market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
/// @return The APR at which borrow interests are accrued by this contract on the WBTC market (with 18 decimals).
function getWBTCNextSupplyAPR(uint256 _amount)
public
view
returns (uint256)
{
return
ILens(LENS).getNextUserSupplyRatePerBlock(
CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
address(this), // the address of the user you want to know the next supply rate of
_amount
) * BLOCKS_PER_YEAR;
}
/// @notice Returns the expected amount of borrow interests accrued by this contract, on the WBTC market, after `_nbBlocks`.
/// @return The expected amount of WBTC borrow interests accrued (in 8 decimals, the number of decimals of WBTC).
function getWBTCExpectedAccruedInterests(uint256 _nbBlocks)
public
view
returns (uint256)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 borrowRatePerBlock = getWBTCBorrowRatePerBlock();
return
(borrowedOnPool + borrowedP2P).mul(borrowRatePerBlock) * _nbBlocks;
}
/// @notice Returns whether this contract is near liquidation (with a 5% threshold) on the WBTC market.
/// @dev The markets borrowed (in this example, WBTC only) need to be virtually updated to compute the correct health factor.
function isApproxLiquidatable() public view returns (bool) {
return
ILens(LENS).getUserHealthFactor(
address(this), // the address of the user you want to know the health factor of
ILens(LENS).getEnteredMarkets(address(this)) // the markets entered by the user, to make sure the health factor accounts for interests accrued
) <= 1.05e18;
}
}
import ethers from "ethers";
const signer = new ethers.Wallet(
process.env.PRIVATE_KEY,
new ethers.providers.JsonRpcBatchProvider(process.env.RPC_URL)
);
const signerAddress = await signer.getAddress();
const cDaiAddress = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643";
const cWbtc2Address = "0xccF4429DB6322D5C611ee964527D42E5d685DD6a";
const lens = new ethers.Contract(
"0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67",
[
"function getTotalBorrow() external view returns (uint256, uint256, uint256)",
"function getTotalMarketBorrow(address) external view returns (uint256, uint256)",
"function getCurrentBorrowBalanceInOf(address, address) external view returns (uint256, uint256, uint256)",
"function getAverageBorrowRatePerBlock(address) external view returns (uint256, uint256, uint256)",
"function getCurrentUserBorrowRatePerBlock(address, address) external view returns (uint256)",
"function getNextUserBorrowRatePerBlock(address, address, uint256) external view returns (uint256, uint256, uint256, uint256)",
],
signer
);
const oracle = new ethers.Contract(
"0x65c816077C29b557BEE980ae3cC2dCE80204A0C5",
["function getUnderlyingPrice(address) external view returns (uint256)"],
signer
);
async function getTotalBorrowUSD() {
const [, , totalBorrowUSD] = await lens.getTotalBorrow();
return Number(ethers.utils.formatUnits(totalBorrowUSD, 18)); // USD amounts are always in 18 decimals
}
async function getTotalDAIMarketBorrow() {
const [borrowedP2P, borrowedOnPool] = await lens.getTotalMarketBorrow(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
return Number(ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), daiDecimals));
}
async function getWBTCBorrowBalance() {
const [borrowedOnPool, borrowedP2P] = await lens.getCurrentBorrowBalanceInOf(
cWbtc2Address, // the WBTC market, represented by the cWBTC2 ERC20 token
signerAddress // the address of the user you want to get the borrow of
);
return Number(ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), wbtcDecimals));
}
async function getWBTCBorrowBalanceUSD() {
const totalMarketBorrow = await getWBTCBorrowBalance();
const oraclePrice = await oracle.getUnderlyingPrice(cWbtc2Address); // in (36 - nb decimals of WBTC = 28) decimals
return totalMarketBorrow * Number(ethers.utils.formatUnits(oraclePrice, 36 - wbtcDecimals));
}
const nbBlocksPerYear = 4 * 60 * 24 * 365.25;
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getDAIAvgBorrowAPR() {
const [avgBorrowRatePerBlock] = await lens.getAverageBorrowRatePerBlock(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
return (
Number(ethers.utils.formatUnits(avgBorrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCBorrowAPR() {
const borrowRatePerBlock = await lens.getCurrentUserBorrowRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress // the address of the user you want to get the borrow rate of
);
return (
Number(ethers.utils.formatUnits(borrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCNextBorrowAPR(amount) {
const [nextBorrowRatePerBlock] = await lens.getNextUserBorrowRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress, // the address of the user you want to get the next borrow rate of
amount
);
return (
Number(ethers.utils.formatUnits(nextBorrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
getTotalBorrowUSD().then((val) => console.log("Total borrow USD", val));
getTotalDAIMarketBorrow().then((val) => console.log("DAI borrow", val));
getWBTCBorrowBalance().then((val) => console.log("WBTC own borrow", val));
getWBTCBorrowBalanceUSD().then((val) => console.log("WBTC own borrow USD", val));
getDAIAvgBorrowAPR().then((val) => console.log("DAI avg borrow APR", val));
getWBTCBorrowAPR().then((val) => console.log("WBTC borrow APR", val));
getWBTCNextBorrowAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next borrow rate", val)
);

Morpho-AaveV2

Solidity
ethers.js
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.16;
import {ILens} from "./interfaces/ILens.sol";
import {IMorpho} from "./interfaces/IMorpho.sol";
import {IPriceOracleGetter} from "./interfaces/aave/IPriceOracleGetter.sol";
import {WadRayMath} from "@morpho-dao/morpho-utils/src/math/WadRayMath.sol";
contract MorphoAaveV2Borrower {
using WadRayMath for uint256;
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant ADAI = 0x028171bCA77440897B824Ca71D1c56caC55b68A3;
address public constant AWBTC = 0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656;
address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;
address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;
ICompoundOracle public immutable ORACLE;
constructor() {
ORACLE = IPriceOracleGetter(
IMorpho(MORPHO).addressesProvider().getPriceOracle()
);
}
/// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-AaveV2.
/// @return borrowedOnPool The amount of WBTC borrowed on AaveV2's pool (with 8 decimals, the number of decimals of WBTC).
/// @return borrowedP2P The amount of WBTC borrowed peer-to-peer through Morpho-AaveV2 (with 8 decimals, the number of decimals of WBTC).
function getWBTCBorrowBalance()
public
view
returns (uint256 borrowedOnPool, uint256 borrowedP2P)
{
(borrowedOnPool, borrowedP2P, ) = ILens(LENS)
.getCurrentBorrowBalanceInOf(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this) // the address of the user you want to know the borrow of
);
}
/// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-AaveV2.
/// @return borrowedOnPoolDAI The DAI amount of WBTC borrowed on AaveV2's pool (with 18 decimals, the number of decimals of DAI).
/// @return borrowedP2PDAI The DAI amount of WBTC borrowed peer-to-peer through Morpho-AaveV2 (with 18 decimals, the number of decimals of DAI).
function getWBTCBorrowBalanceDAI()
public
view
returns (uint256 borrowedOnPoolDAI, uint256 borrowedP2PDAI)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 oraclePrice = ORACLE.getAssetPrice(DAI); // with 18 decimals, whatever the asset
borrowedOnPoolDAI = borrowedOnPool.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
borrowedP2PDAI = borrowedP2P.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
}
/// @notice Returns the average borrow APR experienced on the DAI market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return The APR at which borrow interests are accumulated on average on the DAI market (with 27 decimals).
function getDAIAvgBorrowAPR() public view returns (uint256) {
return
ILens(LENS).getAverageBorrowRatePerYear(
ADAI // the DAI market, represented by the cDAI ERC20 token
);
}
/// @notice Returns the expected APR at which borrow interests are accrued by this contract, on the WBTC market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return The APR at which WBTC borrow interests are accrued (with 27 decimals).
function getWBTCBorrowAPR() public view returns (uint256) {
return
ILens(LENS).getCurrentUserBorrowRatePerYear(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this) // the address of the user you want to know the borrow rate of
);
}
/// @notice Returns the borrow APR this contract will experience (at maximum) if it borrows the given amount from the WBTC market.
/// @dev The borrow rate experienced on a market is specific to each user,
/// dependending on how their borrow is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return nextSupplyAPR The APR at which borrow interests are accrued by this contract on the WBTC market (with 27 decimals).
function getWBTCNextSupplyAPR(uint256 _amount)
public
view
returns (uint256 nextSupplyAPR)
{
(nextSupplyAPR, , , ) = ILens(LENS).getNextUserSupplyRatePerYear(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this), // the address of the user you want to know the next supply rate of
_amount
);
}
/// @notice Returns the expected amount of borrow interests accrued by this contract, on the WBTC market, after `_nbSeconds`.
/// @return The expected amount of WBTC borrow interests accrued (in 8 decimals, the number of decimals of WBTC).
function getWBTCExpectedAccruedInterests(uint256 _nbSeconds)
public
view
returns (uint256)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 borrowRatePerYear = getWBTCBorrowAPR();
return
((borrowedOnPool + borrowedP2P).rayMul(borrowRatePerYear) *
_nbSeconds) / 365.25 days;
}
/// @notice Returns whether this contract is near liquidation (with a 5% threshold) on the WBTC market.
/// @dev The markets borrowed (in this example, WBTC only) need to be virtually updated to compute the correct health factor.
function isApproxLiquidatable() public view returns (bool) {
return
ILens(LENS).getUserHealthFactor(
address(this) // the address of the user you want to know the health factor of
) <= 1.05e18;
}
}
import ethers from "ethers";
const signer = new ethers.Wallet(
process.env.PRIVATE_KEY,
new ethers.providers.JsonRpcBatchProvider(process.env.RPC_URL)
);
const signerAddress = await signer.getAddress();
const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const wbtcAddress = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599";
const aWethAddress = "0x030bA81f1c18d280636F32af80b9AAd02Cf0854e";
const aDaiAddress = "0x028171bCA77440897B824Ca71D1c56caC55b68A3";
const aWbtcAddress = "0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656";
const wbtcDecimals = 8;
const daiDecimals = 18;
const dai = new ethers.Contract("0x6B175474E89094C44Da98b954EedeAC495271d0F", DAIAbi, signer);
const weth = new ethers.Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", WETH9Abi, signer);
const lens = new ethers.Contract("0x507fA343d0A90786d86C7cd885f5C49263A91FF4", LensAbi, signer);
const morpho = new ethers.Contract("0x777777c9898d384f785ee44acfe945efdff5f3e0", MorphoAbi, signer);
const oracle = new ethers.Contract("0xA50ba011c48153De246E5192C8f9258A2ba79Ca9", OracleAbi, signer);
/// QUERY ///
async function getTotalBorrowETH() {
const [, , totalBorrowETH] = await lens.getTotalBorrow();
return Number(ethers.utils.formatUnits(totalBorrowETH, 18)); // ETH amounts are always in 18 decimals
}
async function getTotalBorrowDAI() {
const totalBorrowETH = await getTotalBorrowETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
return totalBorrowETH / Number(ethers.utils.formatUnits(daiOraclePrice, 18)); // ETH amounts are always in 18 decimals
}
async function getTotalDAIMarketBorrow() {
const [borrowedP2P, borrowedOnPool] = await lens.getTotalMarketBorrow(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
return Number(ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), daiDecimals));
}
async function getWBTCBorrowBalance() {
const [borrowedOnPool, borrowedP2P] = await lens.getCurrentBorrowBalanceInOf(
aWbtcAddress, // the WBTC market, represented by the aWBTC ERC20 token
signerAddress // the address of the user you want to get the borrow of
);
return Number(ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), wbtcDecimals));
}
async function getWBTCBorrowBalanceETH() {
const totalMarketBorrow = await getWBTCBorrowBalance();
const wbtcOraclePrice = await oracle.getAssetPrice(wbtcAddress); // in ETH (18 decimals), whatever the market
return totalMarketBorrow * Number(ethers.utils.formatUnits(wbtcOraclePrice, 18));
}
async function getWBTCBorrowBalanceDAI() {
const marketBorrowETH = await getWBTCBorrowBalanceETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
return marketBorrowETH * Number(ethers.utils.formatUnits(daiOraclePrice, 18));
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getDAIAvgBorrowAPR() {
const [avgBorrowRatePerYear] = await lens.getAverageBorrowRatePerYear(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
return Number(ethers.utils.formatUnits(avgBorrowRatePerYear, 27)); // 18 decimals, whatever the market
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCBorrowAPR() {
const borrowRatePerYear = await lens.getCurrentUserBorrowRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress // the address of the user you want to get the borrow rate of
);
return Number(ethers.utils.formatUnits(borrowRatePerYear, 27)); // 18 decimals, whatever the market
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCNextBorrowAPR(amount) {
const [nextBorrowRatePerYear] = await lens.getNextUserBorrowRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress, // the address of the user you want to get the next borrow rate of
amount
);
return Number(ethers.utils.formatUnits(nextBorrowRatePerYear, 27)); // 18 decimals, whatever the market
}
getTotalBorrowETH().then((val) => console.log("Total borrow ETH", val));
getTotalBorrowDAI().then((val) => console.log("Total borrow DAI", val));
getTotalDAIMarketBorrow().then((val) => console.log("DAI borrow", val));
getWBTCBorrowBalance().then((val) => console.log("WBTC own borrow", val));
getWBTCBorrowBalanceETH().then((val) => console.log("WBTC own borrow ETH", val));
getWBTCBorrowBalanceDAI().then((val) => console.log("WBTC own borrow DAI", val));
getDAIAvgBorrowAPR().then((val) => console.log("DAI avg borrow APR", val));
getWBTCBorrowAPR().then((val) => console.log("WBTC borrow APR", val));
getWBTCNextBorrowAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next borrow rate", val)
);