This guide walks through integrating with Moonwell's Morpho Vaults. These vaults accept single-asset deposits and allocate funds across multiple Morpho Blue markets, optimizing yield automatically. They implement the standard ERC-4626 interface.
Deposit underlying tokens into the vault and receive vault shares.
IERC4626 vault =IERC4626(0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca);// mwUSDC on BaseIERC20 underlying =IERC20(vault.asset());uint depositAmount =1000e6;// 1,000 USDC// Approve the vault to pull underlyingunderlying.approve(address(vault), depositAmount);// Deposit and receive sharesuint shares = vault.deposit(depositAmount,address(this));
You can also specify exactly how many shares you want with mint():
Step 2: Query Balances
maxWithdraw reflects available liquidity. If all deposited funds are currently lent out in Morpho markets, withdrawals may be limited until borrowers repay or new deposits come in.
Step 3: Withdraw
Withdraw underlying tokens by specifying either an asset amount or a share amount.
The receiver parameter lets you send withdrawn assets directly to another address. The owner parameter specifies whose shares to burn - if it's not msg.sender, the caller needs an ERC-20 allowance on the vault shares.
Building on Top
Your contract can accept user deposits, route them into a Moonwell vault, and issue its own token on top. Since Morpho vaults are ERC-4626, this works with any standard vault integration.
Product tokens map 1:1 to vault shares. As the vault accrues yield, each share becomes redeemable for more underlying over time.
// Mint a specific number of vault shares
uint sharesToMint = 1000e18;
// previewMint tells you how many assets are needed for that many shares
uint assetsRequired = vault.previewMint(sharesToMint);
underlying.approve(address(vault), assetsRequired);
uint assets = vault.mint(sharesToMint, address(this));
// Shares held by your contract
uint shares = vault.balanceOf(address(this));
// Convert shares to underlying value
uint underlyingValue = vault.convertToAssets(shares);
// Max you can deposit / withdraw right now
uint maxDeposit = vault.maxDeposit(address(this));
uint maxWithdraw = vault.maxWithdraw(address(this));
// Withdraw a specific amount of underlying
uint shares = vault.withdraw(500e6, msg.sender, address(this));
// Or redeem a specific number of shares
uint assets = vault.redeem(shares, msg.sender, address(this));
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
/// @notice Example: a wrapper that deposits into a Moonwell Morpho vault
/// and issues its own token backed by vault shares
contract YieldProduct is ERC20 {
IERC4626 public immutable vault;
IERC20 public immutable asset;
constructor(address _vault) ERC20("Yield Token", "yTKN") {
vault = IERC4626(_vault);
asset = IERC20(vault.asset());
asset.approve(_vault, type(uint).max);
}
function deposit(uint amount) external {
asset.transferFrom(msg.sender, address(this), amount);
// Deposit into Moonwell Morpho vault
uint shares = vault.deposit(amount, address(this));
// Mint product tokens proportional to vault shares received
_mint(msg.sender, shares);
}
function withdraw(uint shareAmount) external {
_burn(msg.sender, shareAmount);
// Redeem vault shares directly to the user
vault.redeem(shareAmount, msg.sender, address(this));
}
/// @notice Total underlying held in the vault
function totalUnderlying() external view returns (uint) {
return vault.convertToAssets(vault.balanceOf(address(this)));
}
}