Skip to main content

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 shares
  • mint(shares, receiver) — mint exact shares, pull required assets
  • withdraw(assets, receiver, owner) — withdraw exact assets, burn required shares
  • redeem(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 cap
  • maxMint(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 interest
  • lend_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}%")