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 max_supply is set to 0, there is no cap (unlimited deposits). Deposits that would push totalSupply above the 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)
Dead Shares
On initialization, the vault mints 1000 shares to the zero address. This is a standard defense against ERC4626 inflation attacks, where an attacker front-runs the first depositor to manipulate pricePerShare and steal funds.
Because of this, pricePerShare does not start at exactly 1e18 — it starts marginally above it. For most integrations this difference is negligible, but protocols that assume a fresh vault has a 1:1 asset-to-share ratio should account for the offset.
# 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):
vault_addr = factory.vaults(i)
vault = Contract(vault_addr)
print(f"Market {i}: {vault.name()}, APR: {vault.lend_apr() / 1e16:.2f}%")