Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 25 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,15 @@ You may find the addresses in [Morpho documentation](https://docs.morpho.org/get

⚠️: These whitelists can't both be empty at the same time.

**Profit Check**: If set, this parameter makes sure to only execute profitable liquidate (including gas costs). This requires to use pricers supported on the configured chains.
**Liquidity venues**:

- `options.checkProfit`: `true` if you want to check liquidations profit. `false` otherwise.
- `options.liquidityVenues`: Array of liquidity venue names for this chain. The order of the array is the order in which venues will be tried.

⚠️: If set to true, each confirgured chain should support at least one of the pricers used by the bot.
**Pricers (optional)**:

- `options.pricers`: Array of pricer names for this chain. The order is the fallback order when pricing assets. Leave undefined or set to an empty array to disable profit check for this chain (the bot will then execute liquidations without checking profitability). If set, the bot will only execute liquidations that are profitable (including gas costs).

⚠️: To enable profit check for this chain, `options.pricers` must be set to an non-empty array. To disable, you can leave it undefined or set it to an empty array.

**Treasury Address (optional)**: If set, the profit of the liquidation will be sent to this address at the end of the transaction. If not set, the bot's EOA will be used.

Expand Down Expand Up @@ -146,7 +150,7 @@ For example, the `uniswapV3` venue has different factory addresses for some chai

### Pricers Configuration

Pricers are explained [below](#pricers). **Pricers are optional**, and don't need to be configured if `options.checkProfit` is set to `false` for every configured chains.
Pricers are explained [below](#pricers). **Pricers are optional**, and don't need to be configured if `options.pricers` is undefined or an empty array for every configured chain.

Some pricers require chain-specific configuration. This is done in the `apps/config/src/pricers/` folder.

Expand Down Expand Up @@ -200,15 +204,17 @@ Liquidity venues can be combined to create more complex strategies. For example,

**If you don't plan on supporting a new liquidity venue, you can ignore this section.**

To add your own venue, you need to create a new folder in the `apps/client/src/liquidityVenues` folder.
This folder should contain one `index.ts` file. In this file you will implement the new liquidity venue class that needs to implements the `LiquidityVenue` interface (located in `apps/client/src/liquidityVenues/liquidityVenue.ts`).
This class will contain the logic of the venue, and needs to export two methods: `supportsRoute`(Returns true if the venue if pair of tokens `src` and `dst` is supported by the venue) and `convert`(Encodes the calls to the related contracts and pushes them to the encoder, and returns the new `src`, `dst`, and `srcAmount`). Both these methods can be async (to allow onchain calls).
To add your own venue:

1. Create a new folder in the `apps/client/src/liquidityVenues` folder. This folder should contain one `index.ts` file. In this file you will implement the new liquidity venue class that implements the `LiquidityVenue` interface (located in `apps/client/src/liquidityVenues/liquidityVenue.ts`). This class will contain the logic of the venue, and needs to export two methods: `supportsRoute` (returns true if the venue supports the pair of tokens `src` and `dst`) and `convert` (encodes the calls to the related contracts and pushes them to the encoder, and returns the new `src`, `dst`, and `srcAmount`). Both these methods can be async (to allow onchain and offchain calls).
2. Add the new venue name to the `LiquidityVenueName` type in `apps/config/src/types.ts`.
3. Add a case for the new venue in the `createLiquidityVenue` function in `apps/client/src/liquidityVenues/factory.ts`.

- If your venue needs any abi, you may add it to a new file named after the venue in the `apps/client/src/abis` folder.
- If your venue needs any ABI, you may add it to a new file named after the venue in the `apps/client/src/abis` folder.

### Configuration

If your venue requires chain-specific configuration, you need to add create a new file in the `apps/config/src/liquidityVenues` folder, named like the venue (e.g. `uniswapV3.ts`).
If your venue requires chain-specific configuration, create a new file in the `apps/config/src/liquidityVenues` folder, named like the venue (e.g. `uniswapV3.ts`).

However, some venues don't need any configuration (ex: erc4626).

Expand All @@ -229,13 +235,12 @@ The bot supports multiple pricers through a fallback mechanism. When attempting

- It iterates through the list of pricers in the order they were provided.
- If a pricer does not support the chain or cannot price the asset, the bot moves to the next pricer.
- It iterates through the list of pricers in the order they were provided.

This continues until a compatible pricer is found, or until the end of the list is reached.

### Profit Check

The bot can evaluate the profitability of a liquidation on a given chain only if `options.checkProfit` has been set to `true` (for this chain)d and at least one pricer has been provided.
The bot can evaluate the profitability of a liquidation on a given chain only if `options.pricers` is set and non-empty for that chain.

In that case, for every liquidatable position detected, the bot will try to price in USD both the liquidation premium (in loan asset) and the gas expense of the transaction.

Expand All @@ -245,31 +250,25 @@ If either asset involved in the liquidation cannot be priced (i.e., not supporte

### Configuration

If your pricer requires chain-specific configuration, you need to add create a new file in the `apps/config/src/pricers` folder, named like the pricer (e.g. `uniswapV3.ts`).
If your pricer requires chain-specific configuration, create a new file in the `apps/config/src/pricers` folder, named like the pricer (e.g. `uniswapV3.ts`).

However, some pricers don't need any configuration (ex: `MorphoApi`).

## Add your own pricer

**If you don't plan on supporting a new pricer venue, you can ignore this section.**

To add your own pricer, you need to create a new folder in the `apps/client/src/pricers` folder.
This folder should contain one `index.ts` file. In this file you will implement the new pricer class that needs to implements the `Pricer` interface (located in `apps/client/src/pricers/Pricer.ts`).
This class will contain the logic of the pricer, and needs to export one method: `price`(Returns the price of the given asset in USD, or `undefined` if the asset is not supported). This methods can be async.

- If your pricer needs any abi, you may add it to a new file named after the pricer in the `apps/client/src/abis` folder.
**If you don't plan on supporting a new pricer, you can ignore this section.**

## Order the liquidity venues
To add your own pricer:

The liquidity venues must be imported into the `apps/client/src/index.ts` file and pushed into the `liquidityVenues` array.
Be careful with the order of the array, as it will be the order in which the venues will be used by the bot.
1. Create a new folder in the `apps/client/src/pricers` folder. This folder should contain one `index.ts` file. In this file you will implement the new pricer class that implements the `Pricer` interface (located in `apps/client/src/pricers/Pricer.ts`). This class will contain the logic of the pricer, and needs to export one method: `price` (returns the price of the given asset in USD, or `undefined` if the asset is not supported). This method can be async.
2. Add the new pricer name to the `PricerName` type in `apps/config/src/types.ts`.
3. Add a case for the new pricer in the `createPricer` function in `apps/client/src/pricers/factory.ts`.

## Order the pricers
- If your pricer needs any ABI, you may add it to a new file named after the pricer in the `apps/client/src/abis` folder.

**If you don't want to check for liquidation profit, you can ignore this section.**
## Liquidity venues and pricers order (per chain)

The pricers must be imported into the `apps/client/src/index.ts` file and pushed into the `pricers` array.
Be careful with the order of the array, as it will be the order in which the pricers will be used by the bot.
The choice and ordering of liquidity venues and pricers is configured **per chain** in `apps/config/config.ts`. For each chain entry in `chainConfigs`, set `options.liquidityVenues` (array of venue names) and optionally `options.pricers` (array of pricer names, or undefined/empty to disable profit check). The order of each array is the order in which venues or pricers will be used. See [Liquidity Venues Configuration](#liquidity-venues-configuration) and [Pricers Configuration](#pricers-configuration) for chain-specific settings (e.g. factory addresses).

## Run the bot

Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ export class LiquidationBot {
badDebtPosition: boolean,
) {
if (this.alwaysRealizeBadDebt && badDebtPosition) return true;
if (this.pricers === undefined) return true;
if (this.pricers === undefined || this.pricers.length === 0) return true;

if (loanAssetBalance.beforeTx === undefined || loanAssetBalance.afterTx === undefined)
return false;
Expand Down
29 changes: 10 additions & 19 deletions apps/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ import { privateKeyToAccount } from "viem/accounts";
import { watchBlocks } from "viem/actions";

import { LiquidationBot, type LiquidationBotInputs } from "./bot";
import { Erc20Wrapper } from "./liquidityVenues/erc20Wrapper";
import { Erc4626 } from "./liquidityVenues/erc4626";
import type { LiquidityVenue } from "./liquidityVenues/liquidityVenue";
import { UniswapV3Venue } from "./liquidityVenues/uniswapV3";
import { UniswapV4Venue } from "./liquidityVenues/uniswapV4";
import { ChainlinkPricer, DefiLlamaPricer } from "./pricers";
import type { Pricer } from "./pricers/pricer";
import { createPricer } from "./pricers";
import { createLiquidityVenue } from "./liquidityVenues";

export const launchBot = (config: ChainConfig) => {
const logTag = `[${config.chain.name} client]: `;
Expand All @@ -23,20 +18,16 @@ export const launchBot = (config: ChainConfig) => {
});

// LIQUIDITY VENUES
const liquidityVenues: LiquidityVenue[] = [];
liquidityVenues.push(new Erc20Wrapper());
liquidityVenues.push(new Erc4626());
liquidityVenues.push(new UniswapV3Venue());
liquidityVenues.push(new UniswapV4Venue());
const liquidityVenues = config.liquidityVenues.map((liquidityVenueName) =>
createLiquidityVenue(liquidityVenueName),
);

// PRICERS
const pricers: Pricer[] = [];
pricers.push(new DefiLlamaPricer());
pricers.push(new ChainlinkPricer());
const pricers = config.pricers
? config.pricers.map((pricerName) => createPricer(pricerName))
: undefined;

if (config.checkProfit && pricers.length === 0) {
throw new Error(`${logTag} You must configure pricers!`);
}
// FlASHBOTS

let flashbotAccount = undefined;
if (config.useFlashbots) {
Expand All @@ -60,7 +51,7 @@ export const launchBot = (config: ChainConfig) => {
executorAddress: config.executorAddress,
treasuryAddress: config.treasuryAddress ?? client.account.address,
liquidityVenues,
pricers: config.checkProfit ? pricers : undefined,
pricers,
flashbotAccount,
alwaysRealizeBadDebt: ALWAYS_REALIZE_BAD_DEBT,
};
Expand Down
39 changes: 39 additions & 0 deletions apps/client/src/liquidityVenues/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { LiquidityVenueName } from "@morpho-blue-liquidation-bot/config";

import { LiquidityVenue } from "./liquidityVenue";
import { Erc20Wrapper } from "./erc20Wrapper";
import { Erc4626 } from "./erc4626";
import { UniswapV3Venue } from "./uniswapV3";
import { UniswapV4Venue } from "./uniswapV4";
import { LiquidSwapVenue } from "./liquidSwap";
import { MidasVenue } from "./midas";
import { PendlePTVenue } from "./pendlePT";
import { OneInch } from "./1inch";

/**
* Creates a liquidity venue instance based on the liquidity venue name from config.
* This factory function avoids circular dependencies by keeping liquidity venue
* class imports in the client package, while config only exports string identifiers.
*/
export function createLiquidityVenue(liquidityVenueName: LiquidityVenueName): LiquidityVenue {
switch (liquidityVenueName) {
case "erc20Wrapper":
return new Erc20Wrapper();
case "erc4626":
return new Erc4626();
case "uniswapV3":
return new UniswapV3Venue();
case "uniswapV4":
return new UniswapV4Venue();
case "liquidSwap":
return new LiquidSwapVenue();
case "midas":
return new MidasVenue();
case "pendlePT":
return new PendlePTVenue();
case "1inch":
return new OneInch();
default:
throw new Error(`Unknown liquidity venue: ${liquidityVenueName}`);
}
}
26 changes: 26 additions & 0 deletions apps/client/src/pricers/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PricerName } from "@morpho-blue-liquidation-bot/config";

import { Pricer } from "./pricer";
import { DefiLlamaPricer } from "./defillama";
import { ChainlinkPricer } from "./chainlink";
import { MorphoApi } from "./morphoApi";
import { UniswapV3Pricer } from "./uniswapV3";
/**
* Creates a pricer instance based on the pricer name from config.
* This factory function avoids circular dependencies by keeping pricer
* class imports in the client package, while config only exports string identifiers.
*/
export function createPricer(pricerName: PricerName): Pricer {
switch (pricerName) {
case "defillama":
return new DefiLlamaPricer();
case "chainlink":
return new ChainlinkPricer();
case "morphoApi":
return new MorphoApi();
case "uniswapV3":
return new UniswapV3Pricer();
default:
throw new Error(`Unknown pricer: ${pricerName}`);
}
}
1 change: 1 addition & 0 deletions apps/client/src/pricers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./chainlink";
export * from "./defillama";
export * from "./morphoApi";
export * from "./uniswapV3";
export * from "./factory";
34 changes: 26 additions & 8 deletions apps/config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ export const chainConfigs: Record<number, Config> = {
additionalMarketsWhitelist: [
"0x1eda1b67414336cab3914316cb58339ddaef9e43f939af1fed162a989c98bc20",
],
checkProfit: true,
liquidityVenues: [
"pendlePT",
"midas",
"1inch",
"erc20Wrapper",
"erc4626",
"uniswapV3",
"uniswapV4",
],
pricers: ["defillama", "chainlink", "uniswapV3"],
liquidationBufferBps: 50,
useFlashbots: true,
blockInterval: 2,
Expand Down Expand Up @@ -69,7 +78,16 @@ export const chainConfigs: Record<number, Config> = {
options: {
vaultWhitelist: ["0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183"],
additionalMarketsWhitelist: [],
checkProfit: true,
liquidityVenues: [
"pendlePT",
"midas",
"1inch",
"erc20Wrapper",
"erc4626",
"uniswapV3",
"uniswapV4",
],
pricers: ["defillama", "chainlink", "uniswapV3"],
liquidationBufferBps: 50,
useFlashbots: false,
blockInterval: 10,
Expand All @@ -94,7 +112,7 @@ export const chainConfigs: Record<number, Config> = {
options: {
vaultWhitelist: "morpho-api",
additionalMarketsWhitelist: [],
checkProfit: false,
liquidityVenues: ["1inch", "erc20Wrapper", "erc4626", "uniswapV3", "uniswapV4"],
liquidationBufferBps: 50,
useFlashbots: false,
blockInterval: 5,
Expand All @@ -119,7 +137,7 @@ export const chainConfigs: Record<number, Config> = {
options: {
vaultWhitelist: "morpho-api",
additionalMarketsWhitelist: [],
checkProfit: false,
liquidityVenues: ["erc20Wrapper", "erc4626", "uniswapV3", "uniswapV4"],
liquidationBufferBps: 50,
useFlashbots: false,
blockInterval: 5,
Expand All @@ -144,7 +162,7 @@ export const chainConfigs: Record<number, Config> = {
options: {
vaultWhitelist: "morpho-api",
additionalMarketsWhitelist: [],
checkProfit: false,
liquidityVenues: ["pendlePT", "1inch", "erc20Wrapper", "erc4626", "uniswapV3", "uniswapV4"],
liquidationBufferBps: 50,
useFlashbots: false,
},
Expand Down Expand Up @@ -173,7 +191,7 @@ export const chainConfigs: Record<number, Config> = {
"0xBC8C37467c5Df9D50B42294B8628c25888BECF61", // Re7 WBTC
],
additionalMarketsWhitelist: [],
checkProfit: false,
liquidityVenues: ["erc20Wrapper", "erc4626", "uniswapV3", "uniswapV4"],
liquidationBufferBps: 50,
useFlashbots: false,
blockInterval: 5,
Expand Down Expand Up @@ -201,8 +219,8 @@ export const chainConfigs: Record<number, Config> = {
"0xFc5126377F0efc0041C0969Ef9BA903Ce67d151e", // Felix USDT
"0x2900ABd73631b2f60747e687095537B673c06A76", // Felix HYPE
],
liquidityVenues: ["liquidSwap", "erc20Wrapper", "erc4626", "uniswapV3"],
additionalMarketsWhitelist: [],
checkProfit: false,
liquidationBufferBps: 50,
useFlashbots: false,
},
Expand All @@ -226,7 +244,7 @@ export const chainConfigs: Record<number, Config> = {
options: {
vaultWhitelist: "morpho-api",
additionalMarketsWhitelist: [],
checkProfit: false,
liquidityVenues: ["erc20Wrapper", "erc4626", "uniswapV3"],
liquidationBufferBps: 50,
useFlashbots: false,
blockInterval: 10,
Expand Down
4 changes: 2 additions & 2 deletions apps/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import dotenv from "dotenv";
import type { Address, Chain, Hex } from "viem";

import { chainConfigs } from "./config";
import type { ChainConfig } from "./types";
import type { ChainConfig, LiquidityVenueName, PricerName } from "./types";

dotenv.config();

Expand Down Expand Up @@ -54,7 +54,7 @@ export function getSecrets(chainId: number, chain?: Chain) {
}

export * from "./chains";
export { chainConfigs, type ChainConfig };
export { chainConfigs, type ChainConfig, type LiquidityVenueName, type PricerName };
export * from "./liquidityVenues";
export * from "./pricers";
export { COOLDOWN_PERIOD, COOLDOWN_ENABLED, ALWAYS_REALIZE_BAD_DEBT } from "./config";
15 changes: 14 additions & 1 deletion apps/config/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import type { Address, Chain, Hex } from "viem";

export type LiquidityVenueName =
| "1inch"
| "erc20Wrapper"
| "erc4626"
| "liquidSwap"
| "midas"
| "pendlePT"
| "uniswapV3"
| "uniswapV4";

export type PricerName = "chainlink" | "defillama" | "morphoApi" | "uniswapV3";

export interface Config {
chain: Chain;
morpho: {
Expand All @@ -25,7 +37,8 @@ export interface Config {
export interface Options {
vaultWhitelist: Address[] | "morpho-api";
additionalMarketsWhitelist: Hex[];
checkProfit: boolean;
liquidityVenues: LiquidityVenueName[];
pricers?: PricerName[];
treasuryAddress?: Address;
liquidationBufferBps?: number;
useFlashbots: boolean;
Expand Down
Loading