Interacting with vaults
Euler Vault Kit (EVK) vaults extend ERC-4626 with borrowing and repayment. This guide covers the direct contract calls and EVC batch patterns used by applications, bots, and smart contracts.
Use the ABI and chain-specific addresses from euler-interfaces. Before submitting a transaction, verify the selected vault's asset, factory provenance, EVC, hooks, caps, collateral configuration, oracle route, liquidity, and governance state.
Deposit assets
An EVK deposit follows ERC-4626: the authenticated caller supplies underlying assets and receiver receives vault shares.
IERC20 asset = IERC20(IEVault(vault).asset());
// Approve the vault to pull the underlying asset.
asset.approve(vault, assets);
// Deposit assets and mint shares to receiver.
uint256 shares = IEVault(vault).deposit(assets, receiver);Use previewDeposit, maxDeposit, and transaction simulation before execution. The actual result remains subject to rounding, caps, hooks, token behavior, and state changes before inclusion.
Permit2 allowances
EVK vaults can pull assets through an existing Permit2 allowance before falling back to the underlying token's transferFrom allowance. A signed Permit2 message does not authorize the vault merely by being passed to deposit: the permit must first be applied to Permit2, either in an earlier call or as part of a reviewed batched flow.
A user normally needs an underlying-token approval to Permit2 once, followed by a scoped Permit2 allowance for the selected vault. Check the Permit2 address configured by the vault, allowance amount, expiration, nonce, chain, token, and spender before signing or submitting anything.
See the Permit2 repository for its interfaces and signature format.
Withdraw or redeem
Use withdraw when specifying an asset amount, or redeem when specifying a share amount:
// Burn enough owner shares to receive an exact asset amount.
uint256 sharesBurned = IEVault(vault).withdraw(assets, receiver, owner);
// Burn an exact share amount and receive the resulting assets.
uint256 assetsReceived = IEVault(vault).redeem(shares, receiver, owner);The authenticated caller must be owner or have sufficient vault-share allowance from owner. A withdrawal can still fail because of insufficient vault cash, account-health checks, hooks, or concurrent state changes. For borrowing accounts, simulate the complete EVC flow rather than relying only on maxWithdraw or maxRedeem.
Do not send underlying assets to an unsigned EVC-derived sub-account. Set receiver to an address that can control the received ERC-20 tokens unless the exact receiver flow is EVC-aware and reviewed.
Borrow assets
Borrowing uses two different vault roles:
- The collateral vault holds the account's collateral shares.
- The borrow vault is the liability vault, becomes the account's controller, and sends its underlying asset to the borrow receiver.
The borrow vault must accept the collateral vault through a nonzero current LTV configuration.
// 1. Deposit the collateral asset.
IERC20 collateralAsset = IERC20(IEVault(collateralVault).asset());
collateralAsset.approve(collateralVault, collateralAmount);
IEVault(collateralVault).deposit(collateralAmount, account);
// 2. Enable collateral and the liability/controller vault.
IEVC(evc).enableCollateral(account, collateralVault);
IEVC(evc).enableController(account, borrowVault);
// 3. Borrow from the liability vault.
uint256 borrowed = IEVault(borrowVault).borrow(borrowAmount, receiver);The debt belongs to the EVC-authenticated account; receiver only controls where the borrowed tokens are sent. Check both borrow and liquidation LTVs, oracle units and freshness, available cash, the borrow cap, interest rate, and resulting account health.
An account can have only one enabled controller at a time. Use separate sub-accounts for positions with different liability vaults.
Repay debt
The caller supplies the borrow asset and the second argument identifies the account whose debt is reduced:
uint256 currentDebt = IEVault(borrowVault).debtOf(debtAccount);
IERC20 borrowAsset = IERC20(IEVault(borrowVault).asset());
// The allowance can include a buffer for interest that accrues before repay.
// repay(max, ...) still pulls only the amount actually owed.
uint256 repaymentAllowance = currentDebt + (currentDebt / 100);
borrowAsset.approve(borrowVault, repaymentAllowance);
// type(uint256).max repays the account's full current debt.
uint256 repaid = IEVault(borrowVault).repay(
type(uint256).max,
debtAccount
);For a partial repayment, pass an amount no greater than the current debt. Do not add an asset-amount buffer to a partial repay, because repaying more than the debt can revert. For a full repayment, approve enough for accrued debt and use type(uint256).max; the vault then pulls the amount actually owed.
Anyone can fund a repayment for debtAccount; the payer and debt account do not need to be the same address. The payer must control the asset balance and applicable vault or Permit2 allowance.
If the payer owns shares of the same borrow vault, repayWithShares can burn shares to reduce debt:
(uint256 sharesBurned, uint256 assetsRepaid) =
IEVault(borrowVault).repayWithShares(
type(uint256).max,
debtAccount
);repayWithShares authenticates the share holder through the EVC context. Simulate it when the share value, debt, or exchange rate may change.
Batch a collateral deposit and borrow
The EVC can enable collateral, deposit, enable the controller, and borrow atomically. Calls targeting the EVC itself use onBehalfOfAccount: address(0); vault calls use the account being authenticated.
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](4);
items[0] = IEVC.BatchItem({
targetContract: address(evc),
onBehalfOfAccount: address(0),
value: 0,
data: abi.encodeCall(
IEVC.enableCollateral,
(account, collateralVault)
)
});
items[1] = IEVC.BatchItem({
targetContract: collateralVault,
onBehalfOfAccount: account,
value: 0,
data: abi.encodeCall(
IEVault.deposit,
(collateralAmount, account)
)
});
items[2] = IEVC.BatchItem({
targetContract: address(evc),
onBehalfOfAccount: address(0),
value: 0,
data: abi.encodeCall(
IEVC.enableController,
(account, borrowVault)
)
});
items[3] = IEVC.BatchItem({
targetContract: borrowVault,
onBehalfOfAccount: account,
value: 0,
data: abi.encodeCall(
IEVault.borrow,
(borrowAmount, receiver)
)
});
IEVC(evc).batch(items);This example assumes the collateral asset allowance already exists. If the flow applies a signed permit, include and validate the correct approval call before the deposit. EVC account and vault-status checks can be deferred until the end of a batch, but each item's ordering, authentication, allowance, and receiver semantics still matter.
Simulate the final batch against the intended chain and block, then re-check the decoded calls before signing.
Flash liquidity
EVault exposes flashLoan(uint256 amount, bytes data). It transfers the requested underlying asset to the EVC-authenticated caller, invokes onFlashLoan(data) on that caller, and requires the vault's asset balance to be restored before the callback completes.
contract FlashBorrower is IFlashLoan {
using SafeERC20 for IERC20;
IEVault public immutable vault;
IERC20 public immutable asset;
constructor(IEVault vault_) {
vault = vault_;
asset = IERC20(vault_.asset());
}
function execute(uint256 amount, bytes calldata userData) external {
// Production code must add its own caller authorization.
vault.flashLoan(amount, abi.encode(amount, userData));
}
function onFlashLoan(bytes memory data) external {
require(msg.sender == address(vault), "unexpected vault");
(uint256 amount, bytes memory userData) =
abi.decode(data, (uint256, bytes));
// Perform the reviewed atomic operation using amount and userData.
asset.safeTransfer(address(vault), amount);
}
}The standard balance check requires repayment in the same transaction. A configured hook can impose additional policy or fees, and the borrower contract must restrict who can start the flow and what external calls can be made. Verify available vault cash and simulate the complete callback path.
Assets and shares
Assets are units of the vault's underlying ERC-20; shares represent proportional ownership of the vault. Their exchange rate changes as interest accrues and fees or losses affect the vault.
Use the ERC-4626 conversion and preview functions rather than copying the vault's internal accounting formula:
uint256 assetsForShares = IEVault(vault).convertToAssets(shares);
uint256 sharesForAssets = IEVault(vault).convertToShares(assets);
uint256 expectedShares = IEVault(vault).previewDeposit(assets);
uint256 sharesToBurn = IEVault(vault).previewWithdraw(assets);Conversions can round differently depending on the operation. Read the underlying and share decimals independently, preserve integer precision, and treat previews as state-dependent estimates rather than execution guarantees.
Integration checklist
Before executing a vault interaction:
- Resolve the chain-specific EVC, vault, asset, ABI, and Permit2 addresses from current sources.
- Verify factory provenance, proxy or implementation status, asset, decimals, and bytecode.
- Check caps, cash, hooks, pause state, controller, collateral set, LTVs, oracle path, and governance.
- Keep the authenticated account, share owner, debt account, token payer, and asset receiver distinct in the integration model.
- Decode every approval and call, simulate the final payload, and enforce slippage or outcome bounds where the operation supports them.
- Re-read live state after confirmation; indexed data and simulations can lag or become stale.