Skip to main content

Track Supply Positions

Learn how to track any position supplied through Morpho, onchain via a smart contract & offchain via ethers.js.

  • For Morpho AaveV2 Optimizer and Morpho CompoundV2 Optimizer, there are lenses deployed.

Morpho's Lens exposes generic information about the protocol, such as the total supply in USD (18 decimals). Anyone can query it offchain through Etherscan (morpho-compound, morpho-aave-V2) or with ethers.js, or onchain 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 is the repository:

GitHub Link

  • For Morpho AaveV3 Optimizer, there is no lens but instead, there are snippets provided. The github repository is here:

GitHub Link

👇 Here are concrete examples 👇

Morpho Aave Optimizer Instances​

  • Morpho AaveV3 Optimizer & Morpho AaveV2 Optimizer are Deployed.

The structure of the following solidity snippets is as follows:

  1. Imports
  2. Contracts
/// IMPORTS ///

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import {IPool, IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPool.sol";
import {IAaveOracle} from "@aave-v3-core/interfaces/IAaveOracle.sol";
import {IMorpho} from "@morpho-aave-v3/interfaces/IMorpho.sol";

import {ERC20} from "@solmate/tokens/ERC20.sol";
import {Types} from "@morpho-aave-v3/libraries/Types.sol";
import {MarketLib} from "@snippets/morpho-aave-v3/libraries/MarketLib.sol";
import {Utils} from "@snippets/morpho-aave-v3/Utils.sol";
import {Math} from "@morpho-utils/math/Math.sol";
import {WadRayMath} from "@morpho-utils/math/WadRayMath.sol";
import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol";
import {ReserveConfiguration} from "@aave-v3-core/protocol/libraries/configuration/ReserveConfiguration.sol";

/// FUNCTIONS ///

/// @title Snippets
/// @author Morpho Labs
/// @custom:contact security@morpho.xyz
/// @notice Code snippets for Morpho AaveV3 Optimizer.
contract Snippets {
using Math for uint256;
using WadRayMath for uint256;
using MarketLib for Types.Market;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;

IMorpho public immutable morpho;
IPoolAddressesProvider public immutable addressesProvider;
IPool public immutable pool;
uint8 public immutable eModeCategoryId;

constructor(address morphoAddress) {
morpho = IMorpho(morphoAddress);
pool = IPool(morpho.pool());
addressesProvider = IPoolAddressesProvider(morpho.addressesProvider());
eModeCategoryId = uint8(morpho.eModeCategoryId());
}


/// @notice Computes and returns the total distribution of supply through Morpho, using virtually updated indexes.
/// @return p2pSupplyAmount The total supplied amount matched peer-to-peer, subtracting the supply delta and the idle supply on Morpho's contract (in base currency).
/// @return poolSupplyAmount The total supplied amount on the underlying pool, adding the supply delta (in base currency).
/// @return idleSupplyAmount The total idle supply amount on the Morpho's contract (in base currency).
/// @return totalSupplyAmount The total amount supplied through Morpho (in base currency).
function totalSupply()
public
view
returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount, uint256 idleSupplyAmount, uint256 totalSupplyAmount)
{
address[] memory marketAddresses = morpho.marketsCreated();

uint256 underlyingPrice;
uint256 nbMarkets = marketAddresses.length;

for (uint256 i; i < nbMarkets; ++i) {
address underlying = marketAddresses[i];

DataTypes.ReserveConfigurationMap memory reserve = pool.getConfiguration(underlying);
underlyingPrice = assetPrice(underlying, reserve.getEModeCategory());
uint256 assetUnit = 10 ** reserve.getDecimals();

(uint256 marketP2PSupplyAmount, uint256 marketPoolSupplyAmount, uint256 marketIdleSupplyAmount) =
marketSupply(underlying);

p2pSupplyAmount += (marketP2PSupplyAmount * underlyingPrice) / assetUnit;
poolSupplyAmount += (marketPoolSupplyAmount * underlyingPrice) / assetUnit;
idleSupplyAmount += (marketIdleSupplyAmount * underlyingPrice) / assetUnit;
}

totalSupplyAmount = p2pSupplyAmount + poolSupplyAmount + idleSupplyAmount;
}


/// @notice Returns the supply rate per year a given user is currently experiencing on a given market.
/// @param underlying The address of the underlying asset.
/// @param user The user to compute the supply rate per year for.
/// @return supplyRatePerYear The supply rate per year the user is currently experiencing (in ray).
function supplyAPR(address underlying, address user) public view returns (uint256 supplyRatePerYear) {
(uint256 balanceInP2P, uint256 balanceOnPool,) = supplyBalance(underlying, user);
(uint256 poolSupplyRate, uint256 poolBorrowRate) = poolAPR(underlying);

Types.Market memory market = morpho.market(underlying);
Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

uint256 p2pSupplyRate = Utils.p2pSupplyAPR(
Utils.P2PRateComputeParams({
poolSupplyRatePerYear: poolSupplyRate,
poolBorrowRatePerYear: poolBorrowRate,
poolIndex: indexes.supply.poolIndex,
p2pIndex: indexes.supply.p2pIndex,
proportionIdle: market.proportionIdle(),
p2pDelta: market.deltas.supply.scaledDelta,
p2pTotal: market.deltas.supply.scaledP2PTotal,
p2pIndexCursor: market.p2pIndexCursor,
reserveFactor: market.reserveFactor
})
);

supplyRatePerYear = Utils.weightedRate(p2pSupplyRate, poolSupplyRate, balanceInP2P, balanceOnPool);
}

/// @notice Computes and returns the total distribution of supply for a given market, using virtually updated indexes.
/// @notice It takes into account the amount of token deposit in supply and in collateral in Morpho.
/// @param underlying The address of the underlying asset to check.
/// @return p2pSupply The total supplied amount (in underlying) matched peer-to-peer, subtracting the supply delta and the idle supply.
/// @return poolSupply The total supplied amount (in underlying) on the underlying pool, adding the supply delta.
/// @return idleSupply The total idle amount (in underlying) on the Morpho contract.
function marketSupply(address underlying)
public
view
returns (uint256 p2pSupply, uint256 poolSupply, uint256 idleSupply)
{
Types.Market memory market = morpho.market(underlying);
Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

p2pSupply = market.trueP2PSupply(indexes);
poolSupply = ERC20(market.aToken).balanceOf(address(morpho));
idleSupply = market.idleSupply;
}

/// @notice Returns the balance in underlying of a given user in a given market.
/// @param underlying The address of the underlying asset.
/// @param user The user to determine balances of.
/// @return balanceInP2P The balance in peer-to-peer of the user (in underlying).
/// @return balanceOnPool The balance on pool of the user (in underlying).
/// @return totalBalance The total balance of the user (in underlying).
function supplyBalance(address underlying, address user)
public
view
returns (uint256 balanceInP2P, uint256 balanceOnPool, uint256 totalBalance)
{
Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

balanceInP2P = morpho.scaledP2PSupplyBalance(underlying, user).rayMulDown(indexes.supply.p2pIndex);
balanceOnPool = morpho.scaledPoolSupplyBalance(underlying, user).rayMulDown(indexes.supply.poolIndex);
totalBalance = balanceInP2P + balanceOnPool;
}


/// @dev Computes and returns the underlying pool rates for a specific market.
/// @param underlying The underlying pool market address.
/// @return poolSupplyRatePerYear The market's pool supply rate per year (in ray).
/// @return poolBorrowRatePerYear The market's pool borrow rate per year (in ray).
function poolAPR(address underlying)
public
view
returns (uint256 poolSupplyRatePerYear, uint256 poolBorrowRatePerYear)
{
DataTypes.ReserveData memory reserve = pool.getReserveData(underlying);
poolSupplyRatePerYear = reserve.currentLiquidityRate;
poolBorrowRatePerYear = reserve.currentVariableBorrowRate;
}

/// @notice Returns the price of a given asset.
/// @param asset The address of the asset to get the price of.
/// @param reserveEModeCategoryId Aave's associated reserve e-mode category.
/// @return price The current price of the asset.
function assetPrice(address asset, uint256 reserveEModeCategoryId) public view returns (uint256 price) {
address priceSource;
if (eModeCategoryId != 0 && reserveEModeCategoryId == eModeCategoryId) {
priceSource = pool.getEModeCategoryData(eModeCategoryId).priceSource;
}

IAaveOracle oracle = IAaveOracle(addressesProvider.getPriceOracle());

if (priceSource != address(0)) {
price = oracle.getAssetPrice(priceSource);
}

if (priceSource == address(0) || price == 0) {
price = oracle.getAssetPrice(asset);
}
}
}

Morpho CompoundV2 Optimizer​

Lens address here.

// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.16;

import {ILens} from "@morpho-org/morpho-core-v1/contracts/compound/interfaces/ILens.sol";
import {IMorpho, ICompoundOracle} from "@morpho-org/morpho-core-v1/contracts/compound/interfaces/IMorpho.sol";

import {CompoundMath} from "@morpho-org/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 CompoundV2 Optimizer.
/// @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 CompoundV2 Optimizer (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 CompoundV2 Optimizer.
/// @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 CompoundV2 Optimizer (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;
}
}

ERC4626 Vaults​

Morpho's vaults are another way to interact with Morpho according to the ERC4626 standard.

// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.13;

import {ISupplyVault} from "@morpho-org/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))
);
}
}