In this tutorial we will discuss how to build a Compound Finance liquidation bot. Compound Finance is one of the leading protocols for lending and borrowing crypto currencies in the decentralized finance (DeFi) ecosystem. It’s an algorithmic money market application where you can deposit crypto and earn interest without having to trust a third party with your funds. In addition you can lock up your crypto as collateral to borrow other assets to use throughout the DeFi space.
Compound is a set of Solidity smart contracts. These smart contracts store and track all assets on Compound. In addition their algorithm adjusts the supply and borrow interest rates based on supply and demand. It is a permission less protocol that allows anyone to connect to the blockchain to supply or borrow crypto. All Compound Finance documentation is located here.
The liquidation bot process below will work with the following applications:
- Compound.finance
- Cream.finance (clone of Compound)
- Other protocols that copied Compound.finance
- If you feel up to it fork their code and create your own clone of the Compound project
How does Compound Finance work?
To supply or borrow assets on Compound you can use their user interface or with code using a smart contract.
Suppling assets on Compound
Users earn interest as soon as they supply their asset to Compound. Interest accrues every Ethereum block (currently ~15 seconds) and users can withdraw their principal plus interest anytime.
Tokens supplied to Compound are tracked in Compounds native tokens called cTokens. cTokens are an ERC20 token that represent a claim to a portion of an asset in Compound.
For example, if ETH is deposited into Compound you will receive cETH in return. If USDC is deposited into Compound you will receive cUSDC in return. This will allow you to redeem cETH for ETH and cUSDC for USDC. Each cToken will earn interest based on their rate of return. In other words, cETH will earn the cETH interest rate, and cUSDC will earn the cUSDC interest rate.
cTokens are redeemable for the portion of the liquidity pool they represent. As borrowing increases and the the pool earns interest the number of cTokens increase (earn interest) and are convertible to more of the underlying asset. In summary one earns interest on Compound by holding the ERC20 cToken.
To supply / lend crypto and earn an interest rate:
- First navigate to Compound.Finance
- Connect your Web 3.0 wallet (MetaMask)
- Then unlock the asset that you wish to supply liquidity
- Sign a transaction through your wallet to make the deposit
- Assets are added to the pool and real time interest is earned
- Now you have cTokens in your wallet to track your portion of the liquidity pool
Borrowing assets on Compound
Compound allows users to borrow crypto assets using a supported asset as collateral. For example, a user holding ETH may supply it to Compound and borrow USDC from Compound immediately. Users that borrow assets from Compound pay a variable interest rate every Ethereum block. This interest payed produces the interest that suppliers earn.
Below are a couple of key terms that are important to understand.
- Collateral – In order to borrow an asset users need to supply another type of crypto as collateral. All loans on the platform are fully collateralized loans. Assets supplied as collateral on Compound earns interest but can not be transferred until the loan is closed.
- Collateral Factor – The maximum amount a user can borrow is limited by the collateral factor of the asset supplied. Each asset on Compound can have a different collateral factor. An assets collateral factor can be obtained using the Comptroller contract. Collateral factors are subject to change by Compound Governance with a minimum transition period of five days. An example of a collateral factor is as follows, a user supplies 100 USDC as collateral and the posted collateral factor for USDC is 50%. The user can borrow at most 50 USDC worth of another asset at any given time.
- Borrowing Balance – The total amount borrowed plus interest.
To borrow crypto and pay interest:
- Navigate to Compound.Finance
- Connect your Web 3.0 wallet (MetaMask)
- Deposit collateral to cover your loan
- You will earn borrowing power
- All asset available for supply will have a different borrowing power.
- Then borrow according to how much borrowing power you have.
- Compound works on the method of over collateralization. Meaning borrowers have to supply more value than they wish to borrow to avoid a liquidation.
The Comptroller smart contract
The comptroller is the risk management smart contract of the Compound application. Every time an account interacts with a cToken the comptroller contract needs to approve or deny the transaction. The comptroller maps prices (using the price oracle) to balances to determine the collateral factor. This contract determines:
- Account collateral required to maintain
- Account liquidation amount
For the Comptroller smart contract address visit Compound’s network documentation.
Compound Price Oracles
The protocol’s Comptroller contract uses Open Price Feed data as a source of truth for prices. Prices are updated by Chainlink Price Feeds. The codebase for these price feeds is hosted on GitHub, and maintained by the community.
The Compound Protocol uses a price feed contract that determines if pricing falls within an acceptable bounds of the time-weighted average price (TWAP) of the token/ETH pair on Uniswap v2. This is a sanity check referred to as the Anchor price.
The Chainlink price feeds submit pricing data for each cToken through an individual ValidatorProxy contract. Each ValidatorProxy is the only valid reporter for the underlying asset price.
For a list of all the on chain pricing contract address visit Compound’s pricing oracle documentation.
Compounds Open Price Feed consists of two main contracts.
- The ValidatorProxy is a contract that calls validate on the UniswapAnchoredView contract. This contract queries Uniswap v2 to determine if a new price is within the Uniswap v2 TWAP anchor. If the price is valid the UniswapAnchoredView is updated with the asset’s new price. If invalid, the price data is not stored.
- UniswapAnchoredView contract’s purpose is to store prices that are within an acceptable bound of the Uniswap time-weighted average price that are signed by a reporter. This contract also contains logic that upscales the posted prices into the format that Compound’s Comptroller expects.
USDC, USDT, and TUSD stable coins are fixed at $1. SAI is fixed at 0.005285 ETH.
To find out more information about the Compound price Oracle:
- Visit Compound for their latest Comptroller smart contract address
- Visit the contract on Etherscan and read it as Proxy
- Scroll down to find the oracle entry
- Click on the address to read the price oracle contract
What is a liquidation?
A liquidation occurs when the value of your borrowed assets (borrowing balance) is greater then your borrowing capacity (collateral factor). This occurs when your collateral drops in value or when the borrowed asset rises too high in value.
If this event occurs arbitrageurs will be competing to liquidate your position. Liquidations are necessary to eliminate risks to the protocol. Liquidators repay up to 50% of the assets borrowed and in return they are eligible to a portion of your collateral at the market price minus a liquidation discount of 5% or more. The liquidation process will continue until the value of your borrowing is back below your borrowing capacity (collateral factor).
The Compound protocol does not rely on a central entity to perform
liquidations. The Compound liquidation process relies on outside 3rd party systems to keep the protocol in balance. It incentivizes decentralized participants to participate (liquidate the collateral, repay the borrowed funds, and in return receive the collateral in another asset with some discount). Any Ethereum user can be a liquidator.
How to identify liquidation opportunities
Information about under collateralized accounts can be obtained from the blockchain or their API. To view a list of potential accounts to be liquidates on Compound visit here. Compound’s smart contracts provide all data required to participate in liquidations.
- Accounts
- Funds supplied
- Outstanding borrows
- Collateral factor
- Relative price of each asset
- etc.
Each asset has a collateral factor. The factor indicates the percentage you can borrow against the collateral supplied and is a number between 1 and 0. Every loan on the platform is initially over collateralized.
To determine an accounts health one needs to analyze the supplied assets, collateral factor, and borrowed assets. Account health is the ratio between total ETH supplied and total ETH borrowed. If an account health score is lower then 1 the account exceeded its ability to borrow. The formula is:
To obtain Compounds account information you can query the smart contracts on the blockchain or you can use the Compound API. You can interact with Compound’s contracts on Mainnet and testnet (Rinkeby, Kovan, Ropsten and Goerli).
Compound smart contract addresses | https://compound.finance/docs |
Compound API | https://compound.finance/docs/api |
How to perform a Compound liquidation
Now that we covered the basics lets review how to perform a Compound liquidation using a bot. Your program will need to search for accounts to liquidate. Then once an account is identified and eligible for a liquidation you need to call the LiquidateBorrow() function in the smart contract.
- Query the Compound API for unhealthy accounts to liquidate
- Connect to the Compound API
- Use setInterval to poll for opportunities using fetch from the browser
- Call the LiquidateBorrow() function in the Compound contract
- Make sure you have the required funds and ETH to pay for gas
- Connect to the blockchain using web3.py or web3js for client communications
- Program uses Compound’s API to find unhealthy accounts
- System identifies a liquidation opportunity
- Send transaction to your smart contract that calls the Compound smart contract
- Transaction is processed and profit is realized
Unhealthy accounts that are eligible to be liquidated on Compound
Compound endpoints support JSON and protobufs binary format. The Account API retrieves information for accounts which have interacted with Compound smart contracts. You can use their API to pull data about a specific account by address, or alternatively, pull data for a list of unhealthy accounts (that is, accounts which are approaching under-collateralization).
//retreives list of accounts and related supply and borrow balances.
fetch("https://api.compound.finance/api/v2/account");
//returns details for given account
fetch("https://api.compound.finance/api/v2/account?addresses[]=0x00..");
The API account request allows a filter for a max_health attribute. Set the filter to less than 1.0 to request accounts subject to a liquidation.
The API account reply contains a health attribute. If this value is less than 1.0, the account is subject to liquidation. The formula is total_collateral_value_in_eth / total_borrow_value_in_eth.
For more information about obtaining account information from Compound visit their API documentation.
How to call the liquidate borrow function using a smart contract
Below is a smart contract you can use to call the LiquidateBorrow function on compound. You can extend this contract to include flash loans or any strategy you desire. To liquidate an account you need to call the LiquidateBorrow function. This function takes the following parameters:
- Address of the account to liquidate
- The repay amount paid by the liquidator
- collateral token with a 5% discount. This discount is defined as the liquidation incentive
Users must approve each cToken contract before calling liquidate (i.e. on the borrowed asset which they are repaying), as they are transferring funds into the contract.
LiquidateBorrow for cERC20
function liquidateBorrow(address borrower, uint amount, address collateral) returns (uint)
LiquidateBorrow for ETH
function liquidateBorrow(address borrower, address cTokenCollateral) payable
The contract below will call functions in the Compound.Finance contracts. Read the comments in the code to understand how the code functions.
pragma solidity ^0.8.6;
//ERC20 interface
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
//Compound cERC20 interface
interface CErc20 {
function balanceOf(address) external view returns (uint);
function mint(uint) external returns (uint);
function exchangeRateCurrent() external returns (uint);
function supplyRatePerBlock() external returns (uint);
function balanceOfUnderlying(address) external returns (uint);
function redeem(uint) external returns (uint);
function redeemUnderlying(uint) external returns (uint);
function borrow(uint) external returns (uint);
function borrowBalanceCurrent(address) external returns (uint);
function borrowRatePerBlock() external view returns (uint);
function repayBorrow(uint) external returns (uint);
function liquidateBorrow(
address borrower,
uint amount,
address collateral
) external returns (uint);
}
//Compound CETH interface
interface CEth {
function balanceOf(address) external view returns (uint);
function mint() external payable;
function exchangeRateCurrent() external returns (uint);
function supplyRatePerBlock() external returns (uint);
function balanceOfUnderlying(address) external returns (uint);
function redeem(uint) external returns (uint);
function redeemUnderlying(uint) external returns (uint);
function borrow(uint) external returns (uint);
function borrowBalanceCurrent(address) external returns (uint);
function borrowRatePerBlock() external view returns (uint);
function repayBorrow() external payable;
}
//Compound Comptroller interface
interface Comptroller {
function markets(address)
external
view
returns (
bool,
uint,
bool
);
function enterMarkets(address[] calldata) external returns (uint[] memory);
function getAccountLiquidity(address)
external
view
returns (
uint,
uint,
uint
);
function closeFactorMantissa() external view returns (uint);
function liquidationIncentiveMantissa() external view returns (uint);
function liquidateCalculateSeizeTokens(
address cTokenBorrowed,
address cTokenCollateral,
uint actualRepayAmount
) external view returns (uint, uint);
}
//Compound price feed interface
interface PriceFeed {
function getUnderlyingPrice(address cToken) external view returns (uint);
}
contract performLiquidationOnCompound {
//set several state variables
IERC20 public token;
CErc20 public ctoken;
Comptroller public comptroller = Comptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
PriceFeed public priceFeed = PriceFeed(0x922018674c12a7F0D394ebEEf9B58F186CdE13c1);
//set token
function settoken(address _token) public {
token = IERC20(_token);
}
//set ctoken - Compound token
function setctoken(address _ctoken) public {
ctoken = CErc20(_ctoken);
}
//call on the comptroller contract
//close factor is the maximum percentage of borrowed tokens that can be repayed
//a percent ranging from 0% to 100% of a liquidatable account’s borrow.
//if you want to view this as a percentage divide the result by 10**18
function closeFactor() external view returns (uint) {
return comptroller.closeFactorMantissa();
}
//call on the comptroller contract
//liquidation incentive is your incentive amount to liquidate the contract
//you are rewarded with a portion of the token that was supplied as collateral
//you receive the collateral at a discount aka your incentive
function liquidationIncentive() external view returns (uint) {
return comptroller.liquidationIncentiveMantissa();
}
//call on the comptroller contract
//get the exact amount of collateral to be liquidated
function amountToBeLiquidatedSieze(address _cToken, address _cTokenCollateral, uint _actualRepayAmount) external view returns (uint) {
(uint error, uint cTokenCollateralAmount) = comptroller
.liquidateCalculateSeizeTokens(
_cToken,
_cTokenCollateral,
_actualRepayAmount
);
require(error == 0, "error");
return cTokenCollateralAmount;
}
//Call liquidate borrow on the cToken contract
//liquidate takes 3 parameters
//the address of the borrower, amount that you will repay, and ctoken address that we will receive in return for liquidating the account
function liquidate(address _borrower, uint _repayAmount, address _cTokenCollateral) external {
//transfer the token borrowed from mesage.sender to this contract
token.transferFrom(msg.sender, address(this), _repayAmount);
//then approve the ctoken repay amount to be spent
token.approve(address(ctoken), _repayAmount);
//call liquidate borrow in the ctoken contract and passing in the necessary parameters using a require statement
//a successful liquidate borrow will return o
//if the call is not successful liquidation failed will display
require(ctoken.liquidateBorrow(_borrower, _repayAmount, _cTokenCollateral) == 0, "liquidation failed");
}
function getPriceFeed(address _ctoken) external view returns (uint) {
return priceFeed.getUnderlyingPrice(_ctoken);
}
}
Try it in Remix
Build a program in Python to complete your Compound liquidity bot
Configuration:
- Node connection information
- Addresses, abi and fee information for Compound
- All token assets to monitor
- Risk management features
Utilities functions:
- Gas prices
- Other utility functions
Pricing
- Monitor the price Oracle
Account Monitoring:
- Create a function to monitor accounts from Compounds API
Trading functions:
- Created a function to submit a transaction to the smart contract above to perform a liquidation
Executing a liquidation is competitive
- There are capital requirements. To liquidate a $1million loan the liquidator needs $1million to do it.
- Consider implementing a flash loan to use for liquidations
- Liquidations are cyclical. They come in bursts with major market moves. Days can elapse with no significant liquidations taking place.
- You need a loan monitoring application to watch for loans that are at risk of becoming under collateralized
- Watching the MakerDAO Price Oracle. Try to back run the oracle and predict when it will change price as well as how this flows through to the liquidation is key.
- Gas prices can erode liquidation profits.
Building a Compound Finance liquidation bot can be rewarding and profitable. You need to be efficient, fast and creative.
Where to run your liquidation bot?
You can run your liquidation bot on any machine that is available 24 hours a day 7 days a week and running an Ethereum node. Some options are as follows:
- Operate a Geth Node and your choice of hardware
- Quick Node and your choice of hardware
- Infra and your choice of hardware
- Alchemy Super node and your choice of hardware
For hardware you can use a local pc, Digital Ocean, AWS, etc. If you plan to build a local server to run consider one of the following options:
Resources
Blockchain Networks
Below is a list of EVM compatible Mainnet and Testnet blockchain networks. Each link contains network configuration, links to multiple faucets for test ETH and tokens, bridge details, and technical resources for each blockchain. Basically everything you need to test and deploy smart contracts or decentralized applications on each chain. For a list of popular Ethereum forums and chat applications click here.
Ethereum test network configuration and test ETH faucet information | |
Optimistic Ethereum Mainnet and Testnet configuration, bridge details, etc. | |
Polygon network Mainnet and Testnet configuration, faucets for test MATIC tokens, bridge details, etc. | |
Binance Smart Chain Mainnet and Testnet configuration, faucets for test BNB tokens, bridge details, etc. | |
Fanton networt Mainnet and Testnet configuration, faucets for test FTM tokens, bridge details, etc. | |
Kucoin Chain Mainnet and Testnet configuration, faucets for test KCS tokens, bridge details, etc. |
Web3 Software Libraries
You can use the following libraries to interact with an EVM compatible blockchain.
- Python: Web3.py A Python library for interacting with Ethereum. Web3.py examples
- Js: web3.js Ethereum JavaScript API
- Java: web3j Web3 Java Ethereum Ðapp API
- PHP: web3.php A php interface for interacting with the Ethereum blockchain and ecosystem.
Nodes
Learn how to run a Geth node. Read getting started with Geth to run an Ethereum node.
Fix a transaction
How to fix a pending transaction stuck on Ethereum or EVM compatible chain