Integration Guide
This guide explains how to integrate with the Ethereum Vault Connector (EVC) in your applications, bots, and smart contracts. You'll learn how to batch operations, use sub-accounts, delegate with operators, perform liquidations, and simulate transactions—all with practical Solidity code examples.
Note: The EVC is the recommended entry point for all advanced interactions with Euler V2 vaults. It provides batching, sub-accounts, operator automation, and atomic cross-vault operations.
Important:
Most ERC-20 tokens are not EVC-aware and are always held by your main address. Sub-accounts are used for internal accounting and position isolation within the EVC and vaults, but not for holding actual ERC-20 tokens.
If you send ERC-20 tokens directly to a sub-account address, those tokens will be lost. Typical ERC-20 contracts do not understand the EVC authentication system and cannot recover or recognize tokens sent to sub-accounts. Always interact with ERC-20 tokens using your main address.
1. Batching Operations
Batching lets you group multiple actions into a single atomic transaction. Use the batch
function and the BatchItem
struct:
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2);
// Example: Deposit and then borrow in a single batch (assumes that the collateral asset was approved and controller and collateral vaults already enabled)
items[0] = IEVC.BatchItem({
targetContract: vault,
onBehalfOfAccount: myAccount,
value: 0,
data: abi.encodeWithSelector(IVault.deposit.selector, depositAmount, myAccount)
});
items[1] = IEVC.BatchItem({
targetContract: vault,
onBehalfOfAccount: myAccount,
value: 0,
data: abi.encodeWithSelector(IVault.borrow.selector, borrowAmount, myAccount)
});
connector.batch(items);
All operations succeed or fail together. You can batch across multiple vaults and even external contracts.
2. Using Sub-accounts
Sub-accounts allow you to isolate positions and strategies. Each address has 256 sub-accounts, which are virtual addresses derived from your main address. This enables you to manage multiple isolated positions under a single wallet, with no extra approvals needed.
How Sub-account Addresses Are Structured and Derived
All 256 sub-accounts belonging to the same owner share the first 19 bytes of their Ethereum address. The only difference between them is the very last byte. This means that, on-chain, you can easily recognize which sub-accounts belong to the same owner: they will look almost identical, except for the final two characters.
How Sub-account Addresses Are Derived
A sub-account address is created by XOR-ing your main address with a number from 0 to 255 (as a uint8
).
- Your main address is sub-account 0 (no change).
- Sub-account 1 is your address XOR 1, sub-account 2 is your address XOR 2, and so on up to 255.
This structure ensures that all your sub-accounts are tightly grouped and easily identifiable, while still being unique and secure.
Example: Deriving a Sub-account Address in Solidity
function getAccount(address owner, uint8 accountId) public pure returns (address) {
return address(uint160(owner) ^ uint160(accountId));
}
// Usage:
address myAddress = /* your main address */;
address account3 = getAccount(myAddress, 3); // This is your 4th sub-account
You can use any of your 256 sub-accounts for deposits, borrows, and other operations, but it's important to understand how onBehalfOfAccount
and i.e. the receiver
parameter interact with sub-accounts and main addresses:
-
Deposits:
onBehalfOfAccount
must be your main address, because ERC-20 tokens are always held by your main address (not by sub-accounts) and are pulled from there.- The
receiver
parameter (in the deposit function) can be a sub-account address, so the deposited shares are credited to the sub-account.
-
Borrows:
onBehalfOfAccount
should be the sub-account for which you want to create the debt position.- The
receiver
parameter (in the borrow function) should be your main address, so the borrowed tokens are sent to an address that actually holds the ERC-20 tokens.
Typically, the onBehalfOfAccount
parameter should be set to the address that would normally be the msg.sender
in a direct interaction with the vault. This is the address that needs to have the necessary permissions and approvals to perform the operation.
Example: Deposit to a Sub-account
// Deposit tokens from your main address, but credit the shares to subAccount3
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](1);
items[0] = IEVC.BatchItem({
targetContract: vault,
onBehalfOfAccount: myAddress, // tokens are pulled from here
value: 0,
data: abi.encodeWithSelector(IVault.deposit.selector, depositAmount, subAccount3) // shares go to sub-account
});
connector.batch(items);
Example: Borrow from a Sub-account
// Borrow on behalf of subAccount3, but send tokens to your main address
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](1);
items[0] = IEVC.BatchItem({
targetContract: vault,
onBehalfOfAccount: subAccount3, // debt is created here
value: 0,
data: abi.encodeWithSelector(IVault.borrow.selector, borrowAmount, myAddress) // tokens sent to main address
});
connector.batch(items);
This distinction is important because most ERC-20 tokens are not EVC-aware and are always held by your main address. Sub-accounts are used for internal accounting and position isolation within the EVC and vaults, but not for holding regular ERC-20 tokens.
3. Account Ownership and Registration
The EVC maintains a mapping of account owners, which is important for resolving the relationship between sub-accounts and their primary (EOA or smart contract) owner. This is especially relevant for integrations, analytics, and off-chain tools.
How Ownership is Registered
An account's owner is registered in the EVC the first time the owner interacts with the EVC (for example, by enabling collateral or a controller, or performing EVC batch or call). Until this happens, the EVC does not know who the owner is.
Direct Vault Interactions and Edge Cases
Accounts can interact with vaults directly, bypassing the EVC. This means:
- An account may not have an owner registered in the EVC until it finally interacts with the EVC (e.g., to enable collateral or a controller for borrowing).
- If an account receives vault shares (e.g., as a transfer) before its owner has ever interacted with the EVC, the EVC will not have an owner registered for that account.
Integration Pattern and Limitations
A typical integration pattern is:
- Use the EVC's
getAccountOwner(address account)
function to resolve the owner. - If the owner is not registered (i.e., the function returns
address(0)
), assume that the account is its own owner.
address owner = evc.getAccountOwner(account);
if (owner == address(0)) {
// Owner not registered; assume the account is its own owner
owner = account;
}
Note: This pattern is widely used, but it is not 100% certain. There may be rare edge cases where the true owner is not the account itself, especially if the ownership is later registered by a different EOA. Always consider the security and UX implications for your application.
4. Operator Delegation
Operators let you delegate control of a sub-account to another address (e.g., a bot or automation contract). You can grant operator rights for a specific sub-account using setAccountOperator
, or for all 256 sub-accounts of an owner using the setOperator
function passing appropriate bit field.
// Grant operator rights for a specific sub-account
evc.setAccountOperator(subAccount, operatorAddress, true);
// Grant operator rights for ALL sub-accounts of the owner
// The address prefix is the first 19 bytes of the owner's address, used by the EVC to group all 256 sub-accounts
// The bitfield is a 256-bit integer where each bit represents a sub-account. Setting a bit to 1 grants operator rights for that sub-account type(uint256).max sets ALL bits to 1, granting rights to all sub-accounts
evc.setOperator(evc.getAddressPrefix(owner), operatorAddress, type(uint256).max);
Warning: Operator delegation is a safety-critical operation. An operator with access to your sub-accounts can move funds, borrow, repay, and perform any action on your behalf. Only delegate operator rights to trusted contracts or EOAs, and regularly review and revoke permissions you no longer need.
Operators can batch, deposit, withdraw, borrow, and more—enabling advanced automation and intent-based strategies. You can revoke operator rights at any time using the same functions with false
or by updating the operator bitfield.
What is an address prefix?
In the EVC, an address prefix is the first 19 bytes of an Ethereum address. All 256 sub-accounts of an owner share the same prefix. The EVC uses this prefix to efficiently manage permissions and operator rights across all sub-accounts. The
getAddressPrefix(address)
function returns this value for any given address.
5. Liquidations with controlCollateral
To perform a liquidation, the controller vault calls controlCollateral
to seize collateral from a borrower's sub-account. Typically, this is done by transferring vault shares from the violator's sub-account to the liquidator using the transfer
function:
// Example: Controller vault seizes collateral shares from violator's sub-account
address collateralVault = /* address of the collateral vault */;
address violator = /* sub-account in violation */;
address liquidator = /* address of the liquidator */;
uint256 seizeShares = /* number of shares to seize */;
// Prepare transfer call data
bytes memory transferData = abi.encodeWithSelector(
IVault.transfer.selector,
liquidator, // recipient of the shares
seizeShares // amount of shares to transfer
);
// Call controlCollateral
connector.controlCollateral(
collateralVault,
violator,
0, // value
transferData
);
This operation is atomic and secure. Only the controller vault can initiate it, and only for accounts that have enabled it as a controller. The transfer function is used here to move shares from the violator's sub-account to the liquidator as part of the liquidation process.
6. Simulating Transactions
You can simulate batches before execution using batchSimulation
:
(
IEVC.BatchItemResult[] memory results,
IEVC.StatusCheckResult[] memory accountChecks,
IEVC.StatusCheckResult[] memory vaultChecks
) = connector.batchSimulation(items);
// Analyze results before sending a real transaction
This is useful for frontends, bots, and risk management tools.
7. Permits (Gasless Transactions)
The EVC supports gasless transactions using EIP-712 permits. This allows users to sign a message off-chain, which can then be submitted by any relayer to execute a batch of operations on their behalf—without the user paying gas directly.
Permits are especially useful for meta-transactions, automation, cross-chain operations. The EVC's permit
function supports both ECDSA (EOA) and ERC-1271 (smart contract wallet) signatures.
How to Use EVC Permits
- Construct the permit message, specifying the signer, sender, nonce, deadline, value, and calldata (typically a batch of operations).
- Sign the message off-chain using EIP-712.
- Submit the permit to the EVC's
permit
function:
evc.permit(
signer, // The user authorizing the action
sender, // The relayer or executor
nonceNamespace, // For replay protection
nonce, // For replay protection
deadline, // Expiry timestamp
value, // ETH value to forward (usually 0)
data, // Encoded calldata (e.g., batch)
signature // EIP-712 signature
);
The EVC will verify the signature, check the nonce and deadline, and then execute the requested operations as if they were sent by the signer.
Security Note: Permits are powerful and should be handled with care. Always verify the nonce and deadline, and never sign a permit you do not fully understand. If a permit is compromised, use Permit Disabled Mode (see below) to protect your account.
Understanding Nonce Namespace
The EVC's permit system uses a nonceNamespace
and nonce
for replay protection and sequencing. The nonceNamespace
allows you to have multiple independent streams of permits for the same account. This is useful if you want to:
- Allow parallel workflows (e.g., one for regular actions, one for high-priority or emergency actions)
- Cancel or replace a specific stream of permits without affecting others
For most simple use cases, you can use a single namespace (e.g., nonceNamespace = 0
) and increment the nonce
for each new permit. For more advanced scenarios, you can assign different namespaces to different workflows or applications.
Tip: If you are unsure, use
nonceNamespace = 0
for all your permits. Only use multiple namespaces if you need parallel, independent streams of permits.
8. Emergency Modes: Lockdown and Permit Disabled
The EVC provides two emergency modes to help users protect their accounts in case of compromise or suspicious activity:
Lockdown Mode
- When enabled, Lockdown Mode restricts all operations for the affected address prefix (all 256 sub-accounts), except for managing operators and nonces.
- No external contract calls or value transfers are allowed, but controllers can still control collateral for the accounts.
- Useful if you suspect a malicious operator or permit has been added.
Enable with:
evc.setLockdownMode(evc.getAddressPrefix(owner), true);
Permit Disabled Mode
- When enabled, this mode prevents execution of any permits signed by the owner for the affected address prefix.
- Useful if you believe a harmful permit message has been signed.
Enable with:
evc.setPermitDisabledMode(evc.getAddressPrefix(owner), true);
Tip: Both modes can be disabled by calling the same functions with
false
.
9. Best Practices
- Always use batching for related operations to save gas and ensure atomicity
- Leverage sub-accounts for risk isolation and strategy separation
- Delegate with operators for automation, but only to trusted contracts
- Simulate complex transactions before execution
- Review EVC security features (Lockdown Mode, Permit Disabled Mode) for extra protection