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:
- For Morpho AaveV3 Optimizer, there is no lens but instead, there are snippets provided. The github repository is here:
👇 Here are concrete examples 👇
Morpho Aave Optimizer Instances​
- Morpho AaveV3 Optimizer & Morpho AaveV2 Optimizer are Deployed.
- V3: Solidity
- V3: Ethers.js
- V2: Solidity
- V2: Ethers.js
The structure of the following solidity snippets is as follows:
- Imports
- 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);
}
}
}
The structure of the following TS snippets is as follows:
- Imports
- Interface
- Functions
- Utils
/// IMPORTS ///
import { BigNumber, providers } from "ethers";
import { constants } from "ethers/lib/index";
import { PercentMath, WadRayMath } from "@morpho-labs/ethers-utils/lib/maths";
import { minBN, pow10 } from "@morpho-labs/ethers-utils/lib/utils";
/// INTERFACE ///
interface P2PRateComputeParams {
/** The pool supply rate per year (in ray). */
poolSupplyRatePerYear: BigNumber;
/** The pool borrow rate per year (in ray). */
poolBorrowRatePerYear: BigNumber;
/** The last stored pool index (in ray). */
poolIndex: BigNumber;
/** The last stored peer-to-peer index (in ray). */
p2pIndex: BigNumber;
/** The delta amount in pool unit. */
p2pDelta: BigNumber;
/** The total peer-to-peer amount in peer-to-peer unit. */
p2pAmount: BigNumber;
/** The index cursor of the given market (in bps). */
p2pIndexCursor: BigNumber;
/** The reserve factor of the given market (in bps). */
reserveFactor: BigNumber;
/** The proportion idle of the given market (in underlying). */
proportionIdle: BigNumber;
}
/// FUNCTIONS ///
/**
* This function retrieves the total supply over the Morpho AaveV3-ETH Optimizer
* markets for both collateral and supply only.
*
* @param provider A provider instance
*/
const getTotalSupply = async (provider: providers.BaseProvider) => {
const { oracle, morphoAaveV3 } = getContracts(provider);
const markets = await morphoAaveV3.marketsCreated();
const marketsData = await Promise.all(
markets.map(async (underlying) => {
const [
{
aToken: aTokenAddress,
indexes: {
supply: { p2pIndex, poolIndex },
},
deltas: {
supply: { scaledDelta, scaledP2PTotal },
},
idleSupply,
},
underlyingPrice,
] = await Promise.all([
morphoAaveV3.market(underlying),
oracle.getAssetPrice(underlying), // TODO: handle if emode
]);
const aToken = AToken__factory.connect(aTokenAddress, provider);
const [decimals, poolSupplyAmount] = await Promise.all([
aToken.decimals(),
aToken.balanceOf(morphoAaveV3.address),
]);
const p2pSupplyAmount = zeroFloorSub(
WadRayMath.rayMul(scaledP2PTotal, p2pIndex),
WadRayMath.rayMul(scaledDelta, poolIndex)
);
return {
p2pSupplyAmount,
poolSupplyAmount,
idleSupply,
underlyingPrice,
decimals,
};
})
);
const amounts = marketsData.reduce(
(acc, { p2pSupplyAmount, poolSupplyAmount, idleSupply, underlyingPrice, decimals }) => {
const toUsd = (amount: BigNumber) => amount.mul(underlyingPrice).div(pow10(decimals));
return {
p2pSupplyAmount: acc.p2pSupplyAmount.add(toUsd(p2pSupplyAmount)),
poolSupplyAmount: acc.poolSupplyAmount.add(toUsd(poolSupplyAmount)),
idleSupply: acc.idleSupply.add(toUsd(idleSupply)),
};
},
{
p2pSupplyAmount: constants.Zero,
poolSupplyAmount: constants.Zero,
idleSupply: constants.Zero,
}
);
/**
* This function gets the total supply for one given market.
*
* @param underlying The address of the underlying token
* @param provider A provider instance
*/
const getTotalMarketSupply = async (
underlying: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3 } = getContracts(provider);
const {
aToken: aTokenAddress,
indexes: {
supply: { p2pIndex, poolIndex },
},
deltas: {
supply: { scaledDelta, scaledP2PTotal },
},
idleSupply,
} = await morphoAaveV3.market(underlying);
const aToken = AToken__factory.connect(aTokenAddress, provider);
const poolSupplyAmount = await aToken.balanceOf(morphoAaveV3.address);
const p2pSupplyAmount = zeroFloorSub(
WadRayMath.rayMul(scaledP2PTotal, p2pIndex),
WadRayMath.rayMul(scaledDelta, poolIndex)
);
return {
p2pSupplyAmount,
poolSupplyAmount,
idleSupply,
totalSupplyAmount: p2pSupplyAmount.add(poolSupplyAmount).add(idleSupply),
};
};
/**
* This function retrieves the supply balance of one given user in one given market.
*
* @param underlying The market to retrieve the supplied liquidity.
* @param user The user address.
* @param provider A provider instance
*
* @returns The matched peer-to-peer amount, the pool amount and the total supply amount.
*/
const getCurrentSupplyBalanceInOf = async (
underlying: string,
user: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3 } = getContracts(provider);
const [
{
supply: { p2pIndex, poolIndex },
},
scaledP2PSupplyBalance,
scaledPoolSupplyBalance,
] = await Promise.all([
morphoAaveV3.updatedIndexes(underlying),
morphoAaveV3.scaledP2PSupplyBalance(underlying, user),
morphoAaveV3.scaledPoolSupplyBalance(underlying, user),
]);
const balanceInP2P = WadRayMath.rayMul(scaledP2PSupplyBalance, p2pIndex);
const balanceOnPool = WadRayMath.rayMul(scaledPoolSupplyBalance, poolIndex);
return {
balanceInP2P,
balanceOnPool,
totalBalance: balanceInP2P.add(balanceOnPool),
};
};
/**
* This function retrieves the collateral balance of one given user in one given market.
*
* @param underlying The market to retrieve the collateral amount.
* @param user The user address.
* @param provider A provider instance
*
* @returns The total collateral of the user.
*/
const getCurrentCollateralBalanceInOf = async (
underlying: string,
user: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3 } = getContracts(provider);
return morphoAaveV3.collateralBalance(underlying, user)
};
/**
* This function retrieves the supply APY of a user on a given market.
*
* @param underlying The market to retrieve the supply APY.
* @param user The user address.
* @param provider A provider instance
*
* @returns The experienced rate and the total balance of the deposited liquidity on this market.
*/
const getCurrentUserSupplyRatePerYear = async (
underlying: string,
user: string,
provider: providers.BaseProvider
) => {
const [{ balanceInP2P, balanceOnPool }, balanceIdle, { p2pSupplyRate, poolSupplyRate }] =
await Promise.all([
getCurrentSupplyBalanceInOf(underlying, user, provider),
getCurrentCollateralBalanceInOf(underlying, user, provider),
getSupplyRatesPerYear(underlying, provider),
]);
const poolAmount = balanceIdle.add(balanceOnPool);
return getWeightedRate(p2pSupplyRate, poolSupplyRate, balanceInP2P, poolAmount);
};
/**
* This function compute the P2P supply rate and returns the result.
*
* @param params The parameters inheriting of the P2PRateComputeParams interface allowing the computation.
* @returns The p2p supply rate per year in _RAY_ units.
*/
const getP2PSupplyRate = ({
poolSupplyRatePerYear,
poolBorrowRatePerYear,
p2pIndexCursor,
p2pIndex,
poolIndex,
proportionIdle,
reserveFactor,
p2pDelta,
p2pAmount,
}: P2PRateComputeParams) => {
let p2pSupplyRate;
if (poolSupplyRatePerYear.gt(poolBorrowRatePerYear)) p2pSupplyRate = poolBorrowRatePerYear;
else {
const p2pRate = getWeightedAvg(poolSupplyRatePerYear, poolBorrowRatePerYear, p2pIndexCursor);
p2pSupplyRate = p2pRate.sub(
PercentMath.percentMul(p2pRate.sub(poolBorrowRatePerYear), reserveFactor)
);
}
if (p2pDelta.gt(0) && p2pAmount.gt(0)) {
const proportionDelta = minBN(
WadRayMath.rayDiv(
// TODO: use of indexDivUp
WadRayMath.rayMul(p2pDelta, poolIndex),
WadRayMath.rayMul(p2pAmount, p2pIndex)
),
WadRayMath.RAY.sub(proportionIdle) // To avoid proportionDelta + proportionIdle > 1 with rounding errors.
);
p2pSupplyRate = WadRayMath.rayMul(
p2pSupplyRate,
WadRayMath.RAY.sub(proportionDelta).sub(proportionIdle)
)
.add(WadRayMath.rayMul(poolSupplyRatePerYear, proportionDelta))
.add(proportionIdle);
}
return p2pSupplyRate;
};
/**
* This function compute the supply rate on a specific asset and returns the result.
*
* @param underlying The market to retrieve the supply APY.
* @param provider A provider instance
*
* @returns The P2P supply rate per year and the pool supply rate per year in _RAY_ units.
*/
const getSupplyRatesPerYear = async (
underlying: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3, pool } = getContracts(provider);
const [
{ currentLiquidityRate, currentVariableBorrowRate },
{
idleSupply,
deltas: {
supply: { scaledDelta, scaledP2PTotal },
},
indexes: {
supply: { p2pIndex, poolIndex },
},
reserveFactor,
p2pIndexCursor,
},
] = await Promise.all([pool.getReserveData(underlying), morphoAaveV3.market(underlying)]);
const totalP2PSupplied: BigNumber = WadRayMath.rayMul(
scaledP2PTotal,
p2pIndex // TODO: use updated index
);
const propIdleSupply = WadRayMath.rayDiv(idleSupply, totalP2PSupplied);
const p2pSupplyRate = await getP2PSupplyRate({
poolSupplyRatePerYear: currentLiquidityRate,
poolBorrowRatePerYear: currentVariableBorrowRate,
poolIndex,
p2pIndex,
proportionIdle: propIdleSupply,
p2pDelta: scaledDelta,
p2pAmount: scaledP2PTotal,
p2pIndexCursor: BigNumber.from(p2pIndexCursor),
reserveFactor: BigNumber.from(reserveFactor),
});
return {
p2pSupplyRate,
poolSupplyRate: currentLiquidityRate,
};
};
/// UTILS ///
/**
* This function is subtracting one number from another, but ensuring that the result is never negative, instead of returning a negative value it will return zero.
*
* @param a A BigNumber.
* @param b A BigNumber.
* @returns A non negative number or 0.
*/
const zeroFloorSub = (a: BigNumber, b: BigNumber) => maxBN(constants.Zero, a.sub(b));
/**
* This function is computing an average rate
* and returns the weighted rate and the total balance.
*
* @param p2pRate The peer-to-peer rate per year, in _RAY_ units
* @param poolRate The pool rate per year, in _RAY_ units
* @param balanceInP2P The underlying balance matched peer-to-peer
* @param balanceOnPool The underlying balance on the pool
*/
const getWeightedRate = async (
p2pRate: BigNumber,
poolRate: BigNumber,
balanceInP2P: BigNumber,
balanceOnPool: BigNumber
) => {
const totalBalance = balanceInP2P.add(balanceOnPool);
if (totalBalance.isZero())
return {
weightedRate: constants.Zero,
totalBalance,
};
return {
weightedRate: p2pRate.mul(balanceInP2P).add(poolRate.mul(balanceOnPool)).div(totalBalance),
totalBalance,
};
};
/**
* This function Executes a weighted average (x * (1 - p) + y * p), rounded up and returns the result.
* TODO: move it to ethers-utils
*
* @param x The first value, with a weight of 1 - percentage.
* @param y The second value, with a weight of percentage.
* @param percentage The weight of y, and complement of the weight of x.
* @returns The result of the weighted average.
*/
const getWeightedAvg = (x: BigNumber, y: BigNumber, percentage: BigNumber) => {
const MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR = constants.MaxUint256.sub(
PercentMath.HALF_PERCENT
);
let z: BigNumber = PercentMath.BASE_PERCENT.sub(percentage);
if (
percentage.gt(PercentMath.BASE_PERCENT) ||
(percentage.gt(0) && y.gt(MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR.div(percentage))) ||
(PercentMath.BASE_PERCENT.gt(percentage) &&
x.gt(MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR.sub(y.mul(percentage)).div(z)))
) {
throw new Error("Underflow or overflow detected");
}
z = x.mul(z).add(y.mul(percentage)).add(PercentMath.HALF_PERCENT).div(PercentMath.BASE_PERCENT);
return z;
};
Lens address here.
// 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-org/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 Optimizer.
/// @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 Optimizer (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 Optimizer.
/// @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 Optimizer (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;
}
}
Lens address here.
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)
);
Morpho CompoundV2 Optimizer​
- Solidity
- ethers.js
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;
}
}
Lens address here.
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 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)
);
ERC4626 Vaults​
Morpho's vaults are another way to interact with Morpho according to the ERC4626 standard.
- Solidity
- ethers.js
// 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))
);
}
}
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)
);