Skip to main content

Overview

The Ethereum Vault Connector (EVC) enables advanced transaction patterns including batching, sub-accounts, and deferred checks. This guide covers how to integrate EVC functionality into your application.

EVC Address

EVC Contract: 0xceAA7cdCD7dDBee8601127a9Abb17A974d613db4

Core Concepts

Batching

Execute multiple operations atomically in a single transaction.

Sub-Accounts

Each address can have up to 256 virtual sub-accounts for position isolation.

Operators

Delegate permissions to trusted addresses for automated operations.

Deferred Checks

Temporarily violate limits as long as final state is valid.

Basic Integration

Connecting to EVC

import { ethers } from 'ethers';

const EVC_ADDRESS = '0xceAA7cdCD7dDBee8601127a9Abb17A974d613db4';
const evcABI = [
  'function batch(bytes[] calldata calls) external',
  'function setOperator(address operator, bool enabled) external',
  // ... other functions
];

const provider = new ethers.JsonRpcProvider('YOUR_RPC_URL');
const signer = await provider.getSigner();
const evc = new ethers.Contract(EVC_ADDRESS, evcABI, signer);

Batching Operations

Simple Batch

async function batchOperations() {
  const vault1 = new ethers.Contract(VAULT1_ADDRESS, vaultABI, signer);
  const vault2 = new ethers.Contract(VAULT2_ADDRESS, vaultABI, signer);
  
  const calls = [
    vault1.interface.encodeFunctionData('deposit', [amount1, userAddress]),
    vault2.interface.encodeFunctionData('borrow', [amount2, userAddress]),
  ];
  
  const tx = await evc.batch(calls);
  await tx.wait();
}

Complex Batch with Multiple Vaults

async function complexBatch() {
  const calls = [];
  
  // Deposit collateral
  calls.push(
    collateralVault.interface.encodeFunctionData('deposit', [collateralAmount, userAddress])
  );
  
  // Borrow asset
  calls.push(
    assetVault.interface.encodeFunctionData('borrow', [borrowAmount, userAddress])
  );
  
  // Swap borrowed asset for more collateral
  calls.push(
    swapRouter.interface.encodeFunctionData('swap', [swapParams])
  );
  
  // Deposit swapped collateral
  calls.push(
    collateralVault.interface.encodeFunctionData('deposit', [newCollateralAmount, userAddress])
  );
  
  const tx = await evc.batch(calls);
  await tx.wait();
}

Sub-Account Operations

Addressing Sub-Accounts

Sub-accounts are addressed as address:subAccountId:
function getSubAccountAddress(userAddress: string, subAccountId: number): string {
  return `${userAddress}:${subAccountId}`;
}

Operations on Sub-Accounts

async function depositToSubAccount(subAccountId: number, amount: bigint) {
  const subAccount = `${userAddress}:${subAccountId}`;
  const vault = new ethers.Contract(vaultAddress, vaultABI, signer);
  
  const tx = await vault.deposit(amount, subAccount);
  await tx.wait();
}

Querying Sub-Account Balances

async function getSubAccountBalance(subAccountId: number) {
  const subAccount = `${userAddress}:${subAccountId}`;
  const vault = new ethers.Contract(vaultAddress, vaultABI, provider);
  
  const balance = await vault.balanceOf(subAccount);
  return balance;
}

Operator Management

Setting an Operator

async function setOperator(operatorAddress: string, enabled: boolean) {
  const tx = await evc.setOperator(operatorAddress, enabled);
  await tx.wait();
}

Operator Operations

Operators can execute transactions on behalf of users:
// As an operator
async function executeAsOperator(userAddress: string) {
  const vault = new ethers.Contract(vaultAddress, vaultABI, operatorSigner);
  
  // Operator can call functions that check operator permissions
  const tx = await vault.deposit(amount, userAddress);
  await tx.wait();
}

Deferred Checks

Deferred checks allow operations that temporarily violate limits:
async function withdrawAndRepay() {
  // This would normally fail because withdrawing would make health score < 1.0
  // But with deferred checks, both operations execute atomically
  
  const calls = [
    vault.interface.encodeFunctionData('withdraw', [withdrawAmount, userAddress]),
    vault.interface.encodeFunctionData('repay', [repayAmount, userAddress]),
  ];
  
  // Final state (after repay) is valid, so transaction succeeds
  const tx = await evc.batch(calls);
  await tx.wait();
}

Advanced Patterns

Leveraged Position Opening

async function openLeveragedPosition(
  collateralAmount: bigint,
  leverage: number
) {
  const borrowAmount = collateralAmount * BigInt(leverage - 1);
  
  const calls = [
    // 1. Deposit initial collateral
    collateralVault.interface.encodeFunctionData('deposit', [collateralAmount, userAddress]),
    
    // 2. Borrow asset
    assetVault.interface.encodeFunctionData('borrow', [borrowAmount, userAddress]),
    
    // 3. Swap borrowed asset for more collateral
    swapRouter.interface.encodeFunctionData('swapExactTokensForTokens', [
      borrowAmount,
      minCollateralOut,
      swapPath,
      userAddress,
      deadline
    ]),
    
    // 4. Deposit additional collateral
    collateralVault.interface.encodeFunctionData('deposit', [additionalCollateral, userAddress]),
  ];
  
  const tx = await evc.batch(calls);
  await tx.wait();
}

Position Rebalancing

async function rebalancePosition() {
  const calls = [
    // Withdraw some collateral
    vault1.interface.encodeFunctionData('withdraw', [amount1, userAddress]),
    
    // Repay some debt
    vault2.interface.encodeFunctionData('repay', [amount2, userAddress]),
    
    // Deposit to different vault
    vault3.interface.encodeFunctionData('deposit', [amount3, userAddress]),
  ];
  
  const tx = await evc.batch(calls);
  await tx.wait();
}

Error Handling

Handling Reverts

async function safeBatch(calls: string[]) {
  try {
    const tx = await evc.batch(calls);
    await tx.wait();
    return { success: true };
  } catch (error) {
    // Parse error reason
    if (error.message.includes('insufficient balance')) {
      return { success: false, reason: 'Insufficient balance' };
    }
    if (error.message.includes('health score')) {
      return { success: false, reason: 'Health score too low' };
    }
    return { success: false, reason: 'Unknown error' };
  }
}

Validating Before Execution

async function validateBatch(calls: string[]) {
  // Simulate the batch call
  try {
    await evc.callStatic.batch(calls);
    return { valid: true };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Gas Optimization

Batch Size

  • Group related operations together
  • Don’t batch too many operations (gas limit)
  • Consider gas costs vs. separate transactions

Sub-Account Usage

  • Use sub-accounts to isolate positions
  • Don’t create unnecessary sub-accounts
  • Consider gas costs of sub-account operations

Security Considerations

Operator Permissions

  • Only grant to trusted addresses
  • Regularly review operator list
  • Revoke unused operators
  • Consider time-limited permissions

Batch Validation

  • Validate all inputs before batching
  • Check return values
  • Handle all possible error cases
  • Test edge cases

Testing

Local Testing

// Fork mainnet
const fork = await ethers.getDefaultProvider('http://localhost:8545');

// Deploy test contracts
// Test batching operations
// Verify atomicity

Integration Testing

  • Test on testnet
  • Verify all operations succeed or fail together
  • Test edge cases
  • Monitor gas usage

Need Help?

If you have questions about EVC integration or need developer assistance:

References