Skip to main content

Liquidation bot

The code for Euler's liquidation bot can be found in the open source github repo here. Below is a description on how the bot works, as well as suggestions and tips for where new integrations would need to be made.

The bulk of the liquidation bot logic can be found in the liquidation_bot.py file, with some periphery/helper functions in the other files in the same folder.

The bot can be run on multiple chains simultaneously - in the config.yaml file simply copy the format of an existing chain under its chain ID, add the relevant contract addresses as well as the relevant RPC node to your .env file. Chain IDs to be included in the bot can be specified in the app/__init__.py file.

How it works

  1. Account Monitoring:

    • The primary way of finding new accounts is scanning for AccountStatusCheck events emitted by the EVC contract to check for new & modified positions.
    • This event is emitted every time a borrow is created or modified, and contains both the account address and vault address.
    • Health scores are calculated using the accountLiquidity implemented by the vaults themselves.
    • Accounts are added to a priority queue based on their health score and position size with a time of next update, with low health accounts being checked most frequently.
    • EVC logs are batched on bot startup to catch up to the current block, then scanned for new events at a regular interval.
  2. Liquidation Opportunity Detection:

    • When an account's health score falls below 1, the bot simulates a liquidation transaction across each collateral asset.
    • The bot gets a quote to swap the collateral assets into the debt assets, and simulates a liquidation transaction on the Liquidator.sol contract.
    • Gas cost is estimated for the liquidation transaction, then checks if the leftover assets after repaying debt is greater than the gas cost when converted to ETH.
    • If this is the case, the liquidation is profitable and the bot will attempt to execute the transaction.
  3. Liquidation Execution - Liquidator.sol:

    • If profitable, the bot constructs a transaction to call the liquidateSingleCollateral function on the Liquidator contract.
    • The Liquidator contract then executes a batch of actions via the EVC containing the following steps:
      1. Enables borrow vault as a controller.

      2. Enable collateral vault as a collateral.

      3. Call liquidate() on the violator's position in the borrow vault, which seizes both the collateral and debt position.

      4. Withdraws specified amount of collateral from the collateral vault to the swapper contract.

      5. Calls the swapper contract with a multicall batch to swap the seized collateral, repay the debt, and sweep any remaining dust from the swapper contract.

      6. Transfers remaining collateral to the profit receiver.

      7. Submit batch to EVC.

    • There is a secondary flow still being developed to use the liquidator contract as an EVC operator, which would allow the bot to operate on behalf of another account and pull the debt position alongside the collateral to the account directly. This flow will be particularly useful for liquidating positions without swapping the collateral to the debt asset, for things such as permissioned RWA liquidations.
  4. Swap Quotation:

    • The bot currently uses Euler's Swap API meta-aggregator to get quotes for swapping seized collateral to repay debt. The open source repository as well as instructions on how to run your own can be found here.
    • Currently the bot withdraws and swaps all of the collateral asset into the debt asset, however an optimization could be made to only withdraw enough collateral to repay the debt, which requires some binary search logic for swap venues that can't properly handle exact output swaps.
  5. Profit Handling:

    • Any profit is sent to a designated receiver address.
    • Profit is sent in the form of overswapped debt asset (see above).
  6. Slack Notifications:

    • The bot can send notifications to a slack channel when unhealthy accounts are detected, when liquidations are performed, and when errors occur.
    • The bot also sends a report of all low health accounts at regularly scheduled intervals, which can be configured in the config.yaml file.
    • In order to receive notifications, a slack channel must be set up and a webhook URL must be provided in the .env file.

How to run the bot

Installation

The bot can be run either via building a docker container or manually. In both instances, it runs via a flask app to expose some endpoints for account health dashboards & metrics.

Before running either, setup a .env file by copying the .env.example file and updating with the relevant contract addresses, an EOA private key & API keys. Then, check config.yaml to make sure parameters, contracts, chains, and ABI paths have been set up correctly.

Running locally

To run locally, we need to install some dependencies and build the contracts. This will setup a python virtual environment for installing dependencies. The below command assumes we have foundry installed, which can installed from the Foundry Book.

Setup:

foundryup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cd redstone_script && npm install && cd ..
forge install && forge build
cd lib/evk-periphery && forge build && cd ../..
mkdir logs state

Run:

python flask run --port 8080

Change the Port number to whatever port is desired for exposing the relevant endpoints from the routes.py file.

Docker

After creating the .env file, the below command will create a container, install all dependencies, and start the liquidation bot: docker compose build --progress=plain && docker compose up

This may require some configuration changes on the Docker image to a basic Python enabled container.

Configuration

  • The bot uses variables from both the config.yaml file and the .env file to configure risk, chain, and address settings and private keys.

Make sure to build the contracts in both src and lib to have the correct ABIs loaded from the evk-periphery installation

Configuration through .env file:

REQUIRED:

  • LIQUIDATOR_EOA, LIQUIDATOR_PRIVATE_KEY - public/private key of EOA that will be used to liquidate

  • RPC_URL - RPC provider endpoint (Infura, Rivet, Alchemy etc.)

  • SWAP_API_URL - URL where you have deployed the Swap API meta-aggregator

OPTIONAL:

  • SLACK_WEBHOOK_URL - Optional URL to post notifications to slack
  • RISK_DASHBOARD_URL - Optional, can include a link in slack notifications to manually liquidate a position

Configuration in config.yaml file:

  • Risk parameters:

    • HS_LIQUIDATION, HS_HIGH_RISK, HS_SAFE - Bounds for interval update timing
    • TEENY, MINI, SMALL, MEDIUM - Size bounds for interval timing
    • TEENY_LIQ, TEENY_HIGH, TEENY_LOW, TEENY_SAFE, etc - Timing intervals depening on position size & HS
  • Reporting & other parameters:

    • LOW_HEALTH_REPORT_INTERVAL - Interval between low health reports
    • SLACK_REPORT_HEALTH_SCORE - Threshold to include an account on the low health report
    • BORROW_VALUE_THRESHOLD - Minimum $ amount to include in report
    • LOGS_PATH, SAVE_STATE_PATH - Path directing to save location for Logs & Save State
    • SAVE_INTERVAL - How often state should be saved
    • PROFIT_RECEIVER - Targeted receiver of any profits from liquidations
    • EVAULT_ABI_PATH, EVC_ABI_PATH, LIQUIDATOR_ABI_PATH - Paths to compiled contracts
  • Chain specific parameters:

    • Specified top level with Chain ID
    • name - Chain name
    • EVC_DEPLOYMENT_BLOCK - Block that the contracs were deployed
    • RPC_NAME - env variable name of relevant RPC URL
    • WETH, EVC, SWAPPER, SWAP_VERIFIER, LIQUIDATOR_CONTRACT, PYTH - Relevant deployed contract addresses.

Deploying

If you want to deploy your own version of the liquidator contract, you can run the command below:

forge script contracts/DeployLiquidator.sol --rpc-url $RPC_URL --broadcast --ffi -vvv --slow

Notes

Pull Oracles

  • Many vaults on Euler are configured with Pull oracles, that must be updated prior to any vault actions taking place. Logic for both Pyth and Redstone pull oracles is implemented in the PullOracleHandler class as well as some specific functions in the Liquidator Contract

Swap API

  • As mentioned previously the bot runs quotes through the Swap API, which is easy to set up locally with your own API keys
  • To add addtional swap venues for specific assets, this would be best done in the Quoter class to check the input/output tokens, and send the request to a new venue after filtering

There are quite a few optimizations/improvements that likely could be made with more time, for instance:

  • Storing enabled collateral/controller within the liquidator contract itself to avoid calls to EVC to check & enable already enabled collaterals
  • Reducing the number of calls made to the RPC with smarter caching
  • Smarter gas price & slippage profitability checks
  • Potentially skipping interaction with Liquidator contract entirely and constructing batch off chain
  • More precise swap calculations to avoid overswapping
  • Deconstruction of Pull oracle batches to avoid unnecessary updates on oracles that aren't being used
  • Secure routing via flashbots/bundling/etc