Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Rohit-KK15/MetaVault-AI/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Vault contract is the core of MetaVault AI’s asset management system. It implements an ERC-4626-inspired vault that accepts user deposits, mints shares, and manages funds across multiple DeFi strategies.
Contract Architecture
The Vault inherits from OpenZeppelin’s battle-tested contracts:
contract Vault is ERC20, Ownable {
using SafeERC20 for IERC20;
IERC20 public immutable asset;
uint256 public performanceFeeBps;
address public feeRecipient;
uint256 public withdrawFeeBps;
mapping(address => uint256) public netDeposited;
mapping(address => uint256) public totalWithdrawn;
}
Source: contracts/Vault.sol:19-27
Key Features
Share-Based Accounting
The vault uses share-based accounting to track user ownership:
- Users deposit assets and receive vault shares
- Share value increases as strategies generate yield
- Shares can be redeemed for proportional assets
Fee Management
- Performance Fee: Charged on harvest profits (default: 10% or 1000 bps)
- Withdrawal Fee: Charged on withdrawals (default: 1% or 100 bps)
Core Functions
Deposit
Accepts user deposits and mints proportional shares:
function deposit(uint256 amount) external returns (uint256) {
uint256 shares = convertToShares(amount);
asset.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, shares);
// Track deposit history
netDeposited[msg.sender] += amount;
emit Deposit(msg.sender, amount, shares);
return shares;
}
Source: contracts/Vault.sol:89-100
Withdraw
Burns shares and returns assets to users:
function withdraw(uint256 shares) external returns (uint256 assetsOut) {
require(shares > 0, "zero shares");
require(balanceOf(msg.sender) >= shares, "not enough shares");
// 1. Convert shares → assets
assetsOut = convertToAssets(shares);
// 2. Burn user's shares
_burn(msg.sender, shares);
// 3. Pull assets from strategies if needed
uint256 vaultBal = asset.balanceOf(address(this));
if (vaultBal < assetsOut) {
uint256 needed = assetsOut - vaultBal;
IStrategyRouter(router).withdrawFromStrategies(needed);
}
// 4. Apply withdrawal fee
uint256 fee = (assetsOut * withdrawFeeBps) / 10000;
uint256 userAmount = assetsOut - fee;
asset.safeTransfer(msg.sender, userAmount);
}
Source: contracts/Vault.sol:103-148
Share Conversion
Convert between shares and assets:
function convertToShares(uint256 assets) public view returns (uint256) {
uint256 s = totalSupply();
return s == 0 ? assets : (assets * s) / totalManagedAssets();
}
function convertToAssets(uint256 shares) public view returns (uint256) {
uint256 s = totalSupply();
return s == 0 ? shares : (shares * totalManagedAssets()) / s;
}
Source: contracts/Vault.sol:57-65
Strategy Integration
Router Connection
The vault connects to a StrategyRouter for fund management:
address public router;
function setRouter(address _router) external onlyOwner {
router = _router;
}
modifier onlyRouter() {
require(msg.sender == router, "not router");
_;
}
Source: contracts/Vault.sol:154-163
Fund Movement
Only the router can move funds between vault and strategies:
// Move funds TO strategy
function moveToStrategy(
address strategy,
uint256 amount
) external onlyRouter {
asset.approve(strategy, amount);
asset.safeTransfer(strategy, amount);
}
// Receive funds FROM strategy
function receiveFromStrategy(uint256 amount) external onlyRouter {
// Strategies call asset.transfer(vault, amount)
}
Source: contracts/Vault.sol:166-182
Total Managed Assets
Calculate total assets across vault and all strategies:
function totalManagedAssets() public view returns (uint256) {
uint256 total = asset.balanceOf(address(this));
if (router != address(0)) {
address[] memory strats = IStrategyRouter(router).getStrategies();
for (uint256 i = 0; i < strats.length; i++) {
total += IStrategy(strats[i]).strategyBalance();
}
}
return total;
}
Source: contracts/Vault.sol:196-208
User Analytics
Growth Tracking
Track individual user profit/loss:
function userGrowth(address user) public view returns (int256) {
uint256 deposited = netDeposited[user];
if (deposited == 0) return 0;
uint256 withdrawn = totalWithdrawn[user];
uint256 currentValue = convertToAssets(balanceOf(user));
int256 pnl = int256(currentValue + withdrawn) - int256(deposited);
return pnl;
}
function userGrowthPercent(address user) external view returns (int256) {
uint256 deposited = netDeposited[user];
if (deposited == 0) return 0;
int256 pnl = userGrowth(user);
return (pnl * 1e18) / int256(deposited);
}
Source: contracts/Vault.sol:67-85
Profit Harvesting
Handle profits from strategy harvests:
function handleHarvestProfit(uint256 profit) external {
require(msg.sender == router, "not router");
if (profit == 0) return;
uint256 fee = (profit * performanceFeeBps) / 10000;
if (fee > 0) {
asset.safeTransfer(feeRecipient, fee);
}
}
Source: contracts/Vault.sol:184-194
Constructor Parameters
Deploy the vault with these parameters:
constructor(
address _asset, // Underlying token (e.g., USDC, LINK)
address _feeRecipient, // Address to receive fees
uint256 _performanceBps, // Performance fee in basis points
uint256 _withdrawFeeBps // Withdrawal fee in basis points
) ERC20("Vault Share Token", "VST") Ownable(msg.sender)
Source: contracts/Vault.sol:33-43
OpenZeppelin Dependencies
The Vault relies on OpenZeppelin v5.4.0:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
Source: contracts/Vault.sol:4-6
Events
event Deposit(address indexed user, uint256 amount, uint256 shares);
event Withdraw(address indexed user, uint256 amount, uint256 shares);
Source: contracts/Vault.sol:30-31
View Functions
function totalAssets() public view returns (uint256);
function getNAV() external view returns (uint256);
function availableLiquidity() external view returns (uint256);
Security Considerations
- Access Control: Only router can move funds to strategies
- SafeERC20: All token transfers use OpenZeppelin’s SafeERC20
- Fee Caps: Consider implementing maximum fee limits
- Reentrancy: Protected by CEI pattern (Checks-Effects-Interactions)
Development Best Practices
- Always use
onlyOwner or onlyRouter modifiers for sensitive functions
- Emit events for all state changes
- Use basis points (10000 = 100%) for fee calculations
- Track user history for accurate analytics
Next Steps