Links

Track supply positions

Learn how to track any position supplied 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 supply 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 supply 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 MorphoCompoundSupplier {
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 supplied by this contract through Morpho-Compound.
/// @return suppliedOnPool The amount of WBTC supplied on Compound's pool (with 8 decimals, the number of decimals of WBTC).
/// @return suppliedP2P The amount of WBTC supplied peer-to-peer through Morpho-Compound (with 8 decimals, the number of decimals of WBTC).
function getWBTCSupplyBalance()
public
view
returns (uint256 suppliedOnPool, uint256 suppliedP2P)
{
(suppliedOnPool, suppliedP2P, ) = ILens(LENS)
.getCurrentSupplyBalanceInOf(
CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
address(this) // the address of the user you want to know the supply of
);
}
​
/// @notice Returns the distribution of WBTC supplied by this contract through Morpho-Compound.
/// @return suppliedOnPoolUSD The USD value of the amount of WBTC supplied on Compound's pool (with 18 decimals, whatever the market).
/// @return suppliedP2PUSD The USD value of the amount of WBTC supplied peer-to-peer through Morpho-Compound (with 18 decimals, whatever the market).
function getWBTCSupplyBalanceUSD()
public
view
returns (uint256 suppliedOnPoolUSD, uint256 suppliedP2PUSD)
{
(uint256 suppliedOnPool, uint256 suppliedP2P) = getWBTCSupplyBalance();
​
uint256 oraclePrice = ORACLE.getUnderlyingPrice(CWBTC2); // with (36 - nb decimals of WBTC = 28) decimals
​
suppliedOnPoolUSD = suppliedOnPool.mul(oraclePrice); // with 18 decimals, whatever the underlying token
suppliedP2PUSD = suppliedP2P.mul(oraclePrice); // with 18 decimals, whatever the underlying token
}
​
/// @notice Returns the average supply rate per block experienced on the DAI market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
/// @return The rate per block at which supply interests are accrued on average on the DAI market (with 18 decimals).
function getDAIAvgSupplyRatePerBlock() public view returns (uint256) {
return
ILens(LENS).getAverageSupplyRatePerBlock(
CDAI // the DAI market, represented by the cDAI ERC20 token
);
}
​
/// @notice Returns the average supply APR experienced on the DAI market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
/// @return The APR at which supply interests are accrued on average on the DAI market (with 18 decimals).
function getDAIAvgSupplyAPR() public view returns (uint256) {
return getDAIAvgSupplyRatePerBlock() * BLOCKS_PER_YEAR;
}
​
/// @notice Returns the supply rate per block this contract experiences on the WBTC market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
/// @return The rate per block at which supply interests are accrued by this contract on the WBTC market (with 18 decimals).
function getWBTCSupplyRatePerBlock() public view returns (uint256) {
return
ILens(LENS).getCurrentUserSupplyRatePerBlock(
CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
address(this) // the address of the user you want to know the supply rate of
);
}
​
/// @notice Returns the expected APR at which supply interests are accrued by this contract, on the WBTC market.
/// @return The APR at which WBTC supply interests are accrued (with 18 decimals).
function getWBTCSupplyAPR() public view returns (uint256) {
uint256 supplyRatePerBlock = getWBTCSupplyRatePerBlock();
​
return supplyRatePerBlock * BLOCKS_PER_YEAR;
}
​
/// @notice Returns the supply APR this contract will experience (at minimum) if it supplies the given amount on the WBTC market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
/// @return The APR at which supply 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 supply interests accrued by this contract, on the WBTC market, after `_nbBlocks`.
/// @return The expected amount of WBTC supply interests accrued (with 8 decimals, the number of decimals of WBTC).
function getWBTCExpectedAccruedInterests(uint256 _nbBlocks)
public
view
returns (uint256)
{
(uint256 suppliedOnPool, uint256 suppliedP2P) = getWBTCSupplyBalance();
uint256 supplyRatePerBlock = getWBTCSupplyRatePerBlock();
​
return
(suppliedOnPool + suppliedP2P).mul(supplyRatePerBlock) * _nbBlocks;
}
}
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(
"0x507fA343d0A90786d86C7cd885f5C49263A91FF4",
[
"function getTotalSupply() external view returns (uint256, uint256, uint256)",
"function getTotalMarketSupply(address) external view returns (uint256, uint256)",
"function getCurrentSupplyBalanceInOf(address, address) external view returns (uint256, uint256, uint256)",
"function getAverageSupplyRatePerBlock(address) external view returns (uint256, uint256, uint256)",
"function getCurrentUserSupplyRatePerBlock(address, address) external view returns (uint256)",
"function getNextUserSupplyRatePerBlock(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 getTotalSupplyUSD() {
const [, , totalSupplyUSD] = await lens.getTotalSupply();
​
return Number(ethers.utils.formatUnits(totalSupplyUSD, 18)); // USD amounts are always in 18 decimals
}
​
async function getTotalDAIMarketSupply() {
const [suppliedP2P, suppliedOnPool] = await lens.getTotalMarketSupply(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
​
return Number(ethers.utils.formatUnits(suppliedP2P.add(suppliedOnPool), daiDecimals));
}
​
async function getWBTCSupplyBalance() {
const [suppliedOnPool, suppliedP2P] = await lens.getCurrentSupplyBalanceInOf(
cWbtc2Address, // the WBTC market, represented by the cWBTC2 ERC20 token
signerAddress // the address of the user you want to get the supply of
);
​
return Number(ethers.utils.formatUnits(suppliedP2P.add(suppliedOnPool), wbtcDecimals));
}
​
async function getWBTCSupplyBalanceUSD() {
const totalMarketSupply = await getWBTCSupplyBalance();
const oraclePrice = await oracle.getUnderlyingPrice(cWbtc2Address); // in (36 - nb decimals of WBTC = 28) decimals
​
return totalMarketSupply * Number(ethers.utils.formatUnits(oraclePrice, 36 - wbtcDecimals));
}
​
const nbBlocksPerYear = 4 * 60 * 24 * 365.25;
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getDAIAvgSupplyAPR() {
const [avgSupplyRatePerBlock] = await lens.getAverageSupplyRatePerBlock(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
​
return (
Number(ethers.utils.formatUnits(avgSupplyRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getWBTCSupplyAPR() {
const supplyRatePerBlock = await lens.getCurrentUserSupplyRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress // the address of the user you want to get the supply rate of
);
​
return (
Number(ethers.utils.formatUnits(supplyRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getWBTCNextSupplyAPR(amount) {
const [nextSupplyRatePerBlock] = await lens.getNextUserSupplyRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress, // the address of the user you want to get the next supply rate of
amount
);
​
return (
Number(ethers.utils.formatUnits(nextSupplyRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
​
getTotalSupplyUSD().then((val) => console.log("Total supply USD", val));
getTotalDAIMarketSupply().then((val) => console.log("DAI supply", val));
getWBTCSupplyBalance().then((val) => console.log("WBTC own supply", val));
getWBTCSupplyBalanceUSD().then((val) => console.log("WBTC own supply USD", val));
getDAIAvgSupplyAPR().then((val) => console.log("DAI avg supply APR", val));
getWBTCSupplyAPR().then((val) => console.log("WBTC supply APR", val));
getWBTCNextSupplyAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next supply 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 MorphoAaveV2Supplier {
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 supplied by this contract through Morpho-AaveV2.
/// @return suppliedOnPool The amount of WBTC supplied on AaveV2's pool (with 8 decimals, the number of decimals of WBTC).
/// @return suppliedP2P The amount of WBTC supplied peer-to-peer through Morpho-AaveV2 (with 8 decimals, the number of decimals of WBTC).
function getWBTCSupplyBalance()
public
view
returns (uint256 suppliedOnPool, uint256 suppliedP2P)
{
(suppliedOnPool, suppliedP2P, ) = ILens(LENS)
.getCurrentSupplyBalanceInOf(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this) // the address of the user you want to know the supply of
);
}
​
/// @notice Returns the distribution of WBTC supplied by this contract through Morpho-AaveV2.
/// @return suppliedOnPoolDAI The DAI amount of WBTC supplied on AaveV2's pool (with 18 decimals, the number of decimals of DAI).
/// @return suppliedP2PDAI The DAI amount of WBTC supplied peer-to-peer through Morpho-AaveV2 (with 18 decimals, the number of decimals of DAI).
function getWBTCSupplyBalanceDAI()
public
view
returns (uint256 suppliedOnPoolDAI, uint256 suppliedP2PDAI)
{
(uint256 suppliedOnPool, uint256 suppliedP2P) = getWBTCSupplyBalance();
​
uint256 oraclePrice = ORACLE.getAssetPrice(DAI); // with 18 decimals, whatever the market
​
suppliedOnPoolDAI = suppliedOnPool.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
suppliedP2PDAI = suppliedP2P.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
}
​
/// @notice Returns the average supply APR experienced on the DAI market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return The APR at which supply interests are accrued on average on the DAI market (with 27 decimals).
function getDAIAvgSupplyAPR() public view returns (uint256) {
return
ILens(LENS).getAverageSupplyRatePerYear(
ADAI // the DAI market, represented by the cDAI ERC20 token
);
}
​
/// @notice Returns the supply APR this contract experiences on the WBTC market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return The APR at which supply interests are accrued by this contract on the WBTC market (with 27 decimals).
function getWBTCSupplyAPR() public view returns (uint256) {
return
ILens(LENS).getCurrentUserSupplyRatePerYear(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this) // the address of the user you want to know the supply rate of
);
}
​
/// @notice Returns the supply APR this contract will experience (at minimum) if it supplies the given amount on the WBTC market.
/// @dev The supply rate experienced on a market is specific to each user,
/// dependending on how their supply is matched peer-to-peer or supplied to the AaveV2 pool.
/// @return nextSupplyAPR The APR at which supply interests would be 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 supply interests accrued by this contract, on the WBTC market, after `_nbSeconds`.
/// @return The expected amount of WBTC supply interests accrued (with 8 decimals, the number of decimals of WBTC).
function getWBTCExpectedAccruedInterests(uint256 _nbSeconds)
public
view
returns (uint256)
{
(uint256 suppliedOnPool, uint256 suppliedP2P) = getWBTCSupplyBalance();
uint256 supplyRatePerYear = getWBTCSupplyAPR();
​
return
((suppliedOnPool + suppliedP2P).rayMul(supplyRatePerYear) *
_nbSeconds) / 365.25 days;
}
}
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("0x507fA343d0A90786d86C7cd885f5C49263A91FF", LensAbi, signer);
const morpho = new ethers.Contract("0x777777c9898d384f785ee44acfe945efdff5f3e0", MorphoAbi, signer);
const oracle = new ethers.Contract("0xA50ba011c48153De246E5192C8f9258A2ba79Ca9", OracleAbi, signer);
​
async function getTotalSupplyETH() {
const [, , totalSupplyETH] = await lens.getTotalSupply();
​
return Number(ethers.utils.formatUnits(totalSupplyETH, 18)); // ETH amounts are always in 18 decimals
}
​
async function getTotalSupplyDAI() {
const totalSupplyETH = await getTotalSupplyETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
​
return totalSupplyETH / Number(ethers.utils.formatUnits(daiOraclePrice, 18)); // ETH amounts are always in 18 decimals
}
​
async function getTotalDAIMarketSupply() {
const [suppliedP2P, suppliedOnPool] = await lens.getTotalMarketSupply(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
​
return Number(ethers.utils.formatUnits(suppliedP2P.add(suppliedOnPool), daiDecimals));
}
​
async function getWBTCSupplyBalance() {
const [suppliedOnPool, suppliedP2P] = await lens.getCurrentSupplyBalanceInOf(
aWbtcAddress, // the WBTC market, represented by the aWBTC ERC20 token
signerAddress // the address of the user you want to get the supply of
);
​
return Number(ethers.utils.formatUnits(suppliedP2P.add(suppliedOnPool), wbtcDecimals));
}
​
async function getWBTCSupplyBalanceETH() {
const totalMarketSupply = await getWBTCSupplyBalance();
const wbtcOraclePrice = await oracle.getAssetPrice(wbtcAddress); // in ETH (18 decimals), whatever the market
​
return totalMarketSupply * Number(ethers.utils.formatUnits(wbtcOraclePrice, 18));
}
​
async function getWBTCSupplyBalanceDAI() {
const wbtcSupplyBalance = await getWBTCSupplyBalanceETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
​
return wbtcSupplyBalance / Number(ethers.utils.formatUnits(daiOraclePrice, 18));
}
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getDAIAvgSupplyAPR() {
const [avgSupplyRatePerYear] = await lens.getAverageSupplyRatePerYear(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
​
return Number(ethers.utils.formatUnits(avgSupplyRatePerYear, 27)); // 27 decimals, whatever the market
}
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getWBTCSupplyAPR() {
const supplyRatePerYear = await lens.getCurrentUserSupplyRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress // the address of the user you want to get the supply rate of
);
​
return Number(ethers.utils.formatUnits(supplyRatePerYear, 27)); // 27 decimals, whatever the market
}
​
// @note The supply rate experienced on a market is specific to each user,
// @note dependending on how their supply is matched peer-to-peer or supplied to the Compound pool.
async function getWBTCNextSupplyAPR(amount) {
const [nextSupplyRatePerYear] = await lens.getNextUserSupplyRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress, // the address of the user you want to get the next supply rate of
amount
);
​
return Number(ethers.utils.formatUnits(nextSupplyRatePerYear, 27)); // 27 decimals, whatever the market
}
​
getTotalSupplyETH().then((val) => console.log("Total supply ETH", val));
getTotalSupplyDAI().then((val) => console.log("Total supply DAI", val));
getTotalDAIMarketSupply().then((val) => console.log("DAI supply", val));
getWBTCSupplyBalance().then((val) => console.log("WBTC own supply", val));
getWBTCSupplyBalanceETH().then((val) => console.log("WBTC own supply ETH", val));
getWBTCSupplyBalanceDAI().then((val) => console.log("WBTC own supply DAI", val));
getDAIAvgSupplyAPR().then((val) => console.log("DAI avg supply APR", val));
getWBTCSupplyAPR().then((val) => console.log("WBTC supply APR", val));
getWBTCNextSupplyAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next supply rate", val)
);

ERC-4626 Vaults

Morpho's vaults are a new way to interact with Morpho according to the ERC-4626 standard. Learn more on the dedicated page.
Solidity
ethers.js
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.13;
​
import {ISupplyVault} from "@morpho-dao/morpho-tokenized-vaults/src/compound/interfaces/ISupplyVault.sol";
​
contract MorphoCompoundVaultSupplier {
address public constant MC_DAI = 0xd99D793B8FDaE42C1867293C172b9CBBD3ea49FF;
​
/// @notice Returns the total balance of DAI this contract has supplied and accrued through the vault.
/// @return The total balance of DAI this contract has supplied and accrued through the vault.
function getDAIBalance() public view returns (uint256) {
return
ISupplyVault(MC_DAI).convertToAssets(
ISupplyVault(MC_DAI).balanceOf(address(this))
);
}
}
import ethers from "ethers";
​
const signer = new ethers.Wallet(
process.env.PRIVATE_KEY,
new ethers.providers.JsonRpcBatchProvider(process.env.RPC_URL)
);
​
const daiDecimals = 18;
const compDecimals = 18;
​
const mcDai = new ethers.Contract(
"0xd99D793B8FDaE42C1867293C172b9CBBD3ea49FF",
SupplyVaultAbi,
signer
);
​
async function getBalanceOfDai(address) {
const shares = await mcDai.balanceOf(address);
const assets = await mcDai.convertToAssets(shares);
​
return Number(ethers.utils.formatUnits(assets, daiDecimals));
}
​
getBalanceOfDai(signer.address).then((val) => console.log("Total DAI balance", val));