Independent open source module (Apache-2.0) for WDK by Tether. Not made or endorsed by Tether or Euler. Built by Liam Lakevold. Download: full source (zip, with tests) or npm tarball. Test output: unit, mainnet fork.

wdk-protocol-lending-euler-evm

Euler v2 lending module for WDK (Wallet Development Kit) by Tether, for EVM wallet accounts.

It gives any WDK EVM wallet (@tetherto/wdk-wallet-evm, and ERC-4337 accounts through the same config override the Aave module uses) the standard WDK lending interface on top of Euler v2: the Euler Vault Kit (EVK) vaults and the Ethereum Vault Connector (EVC). Same package shape and method names as @tetherto/wdk-protocol-lending-aave-evm, scaffolded from the @tetherto/create-wdk-module lending template.

What it does

Why vaults are passed in

Euler v2 is vault based. Each vault holds one asset, and one asset can have many vaults, each with its own oracle, LTVs and interest rate model. A token address alone does not say which vault to use, so the integrator maps tokens to vaults once in the configuration, or passes a vault option per call. EULER_PRESETS[1] holds the Euler Prime vaults on Ethereum (USD₮, USDC, WETH) as an example.

Installation

npm install wdk-protocol-lending-euler-evm @tetherto/wdk-wallet-evm

Usage

import WalletManagerEvm from '@tetherto/wdk-wallet-evm'
import EulerProtocolEvm, { EULER_PRESETS } from 'wdk-protocol-lending-euler-evm'

const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'

const wallet = new WalletManagerEvm(seedPhrase, { provider: 'https://your-ethereum-rpc' })
const account = await wallet.getAccount(0)

const euler = new EulerProtocolEvm(account, { vaults: EULER_PRESETS[1] })

// 1. Deposit WETH and use it as collateral.
const eWETH = await euler.getVaultAddress(WETH)
await account.approve({ token: WETH, spender: eWETH, amount: 10n ** 18n })
await euler.supply({ token: WETH, amount: 10n ** 18n })
await euler.setUseVaultAsCollateral(eWETH, true)

// 2. Borrow 500 USD₮ (one transaction, the controller is enabled inside an EVC batch).
await euler.borrow({ token: USDT, amount: 500_000_000n })

// 3. Check the position.
const { debt, healthFactor } = await euler.getAccountData()

// 4. Repay everything, release the controller, withdraw everything.
const eUSDT = await euler.getVaultAddress(USDT)
await account.approve({ token: USDT, spender: eUSDT, amount: debt + 1_000_000n })
await euler.repay({ token: USDT, amount: 2n ** 256n - 1n })
await euler.disableController(eUSDT)
await euler.withdraw({ token: WETH, amount: 2n ** 256n - 1n })

Every write method accepts the same optional second argument as the Aave module, which an ERC-4337 account uses to override its paymaster or sponsorship configuration. Standard accounts ignore it.

API

Method Notes
new EulerProtocolEvm(account, { vaults?, gasLimitBuffer? }) vaults: { [token]: vault }. gasLimitBuffer: percent added to the gas estimate for standard accounts (default 20, 0 to disable). Full or read-only account.
supply({ token, amount, onBehalfOf?, vault? }, config?) vault.deposit(amount, receiver). Approve the vault first.
withdraw({ token, amount, to?, vault? }, config?) vault.withdraw, or vault.redeem(all shares) for MaxUint256.
borrow({ token, amount, vault? }, config?) EVC batch([enableController?, borrow]). onBehalfOf must be omitted or equal the wallet: Euler debt always belongs to the borrower.
repay({ token, amount, onBehalfOf?, vault? }, config?) vault.repay(amount, debtor). MaxUint256 repays the full debt. Approve the vault first.
quoteSupply / quoteWithdraw / quoteBorrow / quoteRepay { fee }. Works with read-only accounts.
setUseVaultAsCollateral(vault, useAsCollateral, config?) EVC enableCollateral / disableCollateral.
disableController(vault, config?) Only once the debt in that vault is zero.
getAccountData(account?) { controllers, collaterals, collateralValue, borrowCollateralValue, liabilityValue, debt, healthFactor }.
getSupplied(token, { vault?, account? }) Deposit value in the token's base unit.
getVaultAddress(token, vault?) The vault used for a token.

Tests

npm install
npm test          # 46 offline unit tests: validation, read-only guards, pre-flight checks, gas buffer, exact calldata of every call
npm run lint      # standard
npm run build:types

End to end against the real Euler Prime vaults on an Ethereum mainnet fork, with a real WDK wallet:

anvil --fork-url <ethereum rpc> --port 18547 --chain-id 1
FORK_RPC=http://127.0.0.1:18547 npm run test:fork

The fork suite (16 tests) wraps ETH, supplies 1 WETH to eWETH-2, enables it as collateral, borrows 500 USD₮ from eUSDT-2 in one transaction, checks the health factor, refuses a borrow from a second vault, lets an hour of interest accrue, refuses a repay above the debt, repays the full debt with MaxUint256, withdraws partially and then fully while the controller is still enabled (EVK reports maxRedeem = 0 in that state), and disables the controller. It also checks that an over-limit borrow and an unhealthy collateral withdrawal are rejected at simulation before signing, and, impersonating the vault governor on the fork, that a deposit disabled through hookConfig is caught before sending.

Notes and limits

License

Apache-2.0

Source files