Vault Integration Guide
Llamalend v2 vaults are standard ERC4626 vaults, so integrating them is straightforward — any protocol or aggregator that supports ERC4626 can plug in directly.
Depositing and Withdrawing
Use the standard ERC4626 methods on the Vault contract:
deposit(assets, receiver)— deposit borrowed tokens, receive vault sharesmint(shares, receiver)— mint exact shares, pull required assetswithdraw(assets, receiver, owner)— withdraw exact assets, burn required sharesredeem(shares, receiver, owner)— burn exact shares, receive corresponding assets
All preview functions (previewDeposit, previewMint, previewWithdraw, previewRedeem) and limit functions (maxDeposit, maxMint, maxWithdraw, maxRedeem) are implemented per the ERC4626 spec.
On-chain (Vyper):
from ethereum.ercs import IERC20
from ethereum.ercs import IERC4626
vault: IERC4626 = IERC4626(vault_addr)
asset: IERC20 = IERC20(staticcall vault.asset())
# Deposit 1000 USDC into the vault
amount: uint256 = 1000 * 10 ** 6
extcall asset.approve(vault_addr, amount)
shares: uint256 = extcall vault.deposit(amount, msg.sender)
# Withdraw all — burn shares, receive assets
assets: uint256 = extcall vault.redeem(shares, msg.sender, msg.sender)
Off-chain (Python / ape):
vault = Contract(vault_address)
asset = Contract(vault.asset())
amount = 1000 * 10 ** asset.decimals()
asset.approve(vault.address, amount, sender=account)
vault.deposit(amount, account, sender=account)
Supply Caps
Each vault has a DAO-configurable max_supply that limits total deposits. Integrators should check available capacity before depositing:
maxDeposit(receiver)— returns the maximum assets depositable, accounting for the supply capmaxMint(receiver)— returns the maximum shares mintable, accounting for the supply cap
If maxSupply is max(uint256), deposits are unlimited. A value of 0 disables new deposits. Deposits that would push total managed assets above a finite cap will revert.
# Check remaining capacity before depositing
max_assets: uint256 = staticcall vault.maxDeposit(msg.sender)
assert amount <= max_assets, "exceeds supply cap"
extcall vault.deposit(amount, msg.sender)
Virtual Shares
The vault includes 1000 virtual shares in its conversion math. This is a defense against ERC4626 inflation attacks, where an attacker front-runs the first depositor to manipulate the share price.
These shares are not minted to the zero address and are not included in totalSupply. A fresh Vault therefore does not begin with a 1:1 asset-to-share ratio. Integrations must use the conversion and preview functions instead of assuming one.
# Use convertToShares / convertToAssets instead of assuming 1:1
shares: uint256 = staticcall vault.convertToShares(assets)
assets: uint256 = staticcall vault.convertToAssets(shares)
Yield Accrual
Yield accrues passively through the vault's rising pricePerShare — no claiming or staking required. Useful view functions:
pricePerShare()— current share price (scaled to 1e18), increases over time as borrowers pay interestlend_apr()— annualized yield for lenders after utilization and admin fees (scaled to 1e18)borrow_apr()— current annualized borrow rate (scaled to 1e18)
vault = Contract(vault_address)
price = vault.pricePerShare() # e.g. 1_023_000_000_000_000_000 (1.023)
lend = vault.lend_apr() # e.g. 52_000_000_000_000_000 (5.2%)
borrow = vault.borrow_apr() # e.g. 78_000_000_000_000_000 (7.8%)
Liquidity Considerations
Withdrawals are limited by available liquidity — tokens currently lent out to borrowers cannot be withdrawn until repaid. The maxWithdraw and maxRedeem functions account for this automatically. Integrators building on top of the vaults should handle partial withdrawal availability gracefully.
available: uint256 = staticcall vault.maxWithdraw(msg.sender)
if available < desired_amount:
# Only withdraw what's available now
extcall vault.withdraw(available, msg.sender, msg.sender)
else:
extcall vault.withdraw(desired_amount, msg.sender, msg.sender)
Market Discovery
Use the LendFactory to enumerate available markets and find vault addresses programmatically.
factory = Contract(factory_address)
n = factory.market_count()
for i in range(n):
market = factory.markets(i)
vault_addr = market.vault
vault = Contract(vault_addr)
print(f"Market {i}: {vault.name()}, APR: {vault.lend_apr() / 1e16:.2f}%")