Liquidation Bot
Build a Lotus liquidation bot that monitors positions, encodes lmData, executes liquidations, and uses flash-funded callbacks with profitability checks.
1
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const lotus = new ethers.Contract(LOTUS_ADDRESS, lotusAbi, signer);
const ORACLE_PRICE_SCALE = 10n ** 36n;
const WAD = 10n ** 18n;
async function findUnhealthyPositions(marketId: string, marketParams: any) {
const numTranches = await lotus.getNumMarketTranches(marketId);
for (let t = 0; t < numTranches; t++) {
// Use accrueInterestAndReturnMaxBorrow for accurate health checks
const borrowers = await getActiveBorrowers(marketId, t); // from indexed events
for (const borrower of borrowers) {
const position = await lotus.getPosition(marketId, borrower);
const borrowShares = position.borrowShares[t];
if (borrowShares === 0n) continue;
const tranches = await lotus.getMarketTranches(marketId);
const tranche = tranches[t];
const borrowed = borrowShares * tranche.trancheBorrowAssets / tranche.trancheBorrowShares;
const oracle = new ethers.Contract(marketParams.oracles[t], oracleAbi, provider);
const price = await oracle.price();
const collateral = position.collateral[t];
const collateralValue = collateral * price / ORACLE_PRICE_SCALE;
const maxBorrow = collateralValue * marketParams.lltvs[t] / WAD;
if (borrowed > maxBorrow) {
console.log(`Unhealthy: ${borrower} tranche ${t}`);
await executeLiquidation(marketParams, t, borrower, borrowShares, price);
}
}
}
}2
struct BaseLiquidationParams {
uint256 seizedAssets; // Collateral to seize (0 if using repaidShares)
uint256 repaidShares; // Borrow shares to repay (0 if using seizedAssets)
bytes callbackData; // Optional callback data for ILotusLiquidateCallback
}bytes memory lmData = abi.encode(BaseLiquidationParams({
seizedAssets: 5e18, // Seize 5 WETH of collateral
repaidShares: 0,
callbackData: ""
}));bytes memory lmData = abi.encode(BaseLiquidationParams({
seizedAssets: 0,
repaidShares: sharesToRepay,
callbackData: ""
}));const lmData = ethers.AbiCoder.defaultAbiCoder().encode(
["tuple(uint256 seizedAssets, uint256 repaidShares, bytes callbackData)"],
[{ seizedAssets: collateralToSeize, repaidShares: 0n, callbackData: "0x" }]
);3
(uint256 seizedAssets, uint256 repaidAssets) = lotus.liquidate(
marketParams,
trancheIndex,
borrower,
"", // irmData (empty for AdaptiveLinearKinkIrm)
lmData
);async function executeLiquidation(
marketParams: any,
trancheIndex: number,
borrower: string,
borrowShares: bigint,
price: bigint
) {
// Encode lmData — repay all borrow shares
const lmData = ethers.AbiCoder.defaultAbiCoder().encode(
["tuple(uint256,uint256,bytes)"],
[[0n, borrowShares, "0x"]]
);
const tx = await lotus.liquidate(
marketParams,
trancheIndex,
borrower,
"0x", // irmData
lmData
);
const receipt = await tx.wait();
console.log(`Liquidated ${borrower}: tx ${receipt.hash}`);
}4
contract FlashLiquidator is ILotusLiquidateCallback {
ILotus public immutable lotus;
ISwapRouter public immutable router;
function liquidate(
MarketParams calldata marketParams,
uint256 trancheIndex,
address borrower,
uint256 seizedAssets
) external {
bytes memory callbackData = abi.encode(msg.sender);
bytes memory lmData = abi.encode(BaseLiquidationParams({
seizedAssets: seizedAssets,
repaidShares: 0,
callbackData: callbackData
}));
lotus.liquidate(marketParams, trancheIndex, borrower, "", lmData);
}
function onLotusLiquidate(uint256 repaidAssets, bytes calldata data) external {
// 1. At this point we have received seized collateral.
// 2. Swap collateral → loan token to cover repaidAssets.
address collateralToken = /* from context */;
address loanToken = /* from context */;
IERC20(collateralToken).approve(address(router), type(uint256).max);
router.exactOutputSingle(/* swap params to get repaidAssets of loanToken */);
// 3. Approve Lotus to pull repaidAssets of loan token.
IERC20(loanToken).approve(address(lotus), repaidAssets);
// 4. Profit remains in this contract.
}
}Profitability Calculation
See Also
Last updated

