Skip to content

Commit

Permalink
add fixed price liquidation contracts to liquidator bot
Browse files Browse the repository at this point in the history
  • Loading branch information
chuckbergeron committed Oct 30, 2024
1 parent b9ed173 commit 1cd1da9
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 33 deletions.
59 changes: 59 additions & 0 deletions packages/library/src/abis/FixedPriceLiquidationPairFactoryAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export const FixedPriceLiquidationPairFactoryAbi = [
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'contract FixedPriceLiquidationPair',
name: 'pair',
type: 'address',
},
{
indexed: false,
internalType: 'contract ILiquidationSource',
name: 'source',
type: 'address',
},
{ indexed: true, internalType: 'address', name: 'tokenIn', type: 'address' },
{ indexed: true, internalType: 'address', name: 'tokenOut', type: 'address' },
{ indexed: false, internalType: 'uint256', name: 'targetAuctionPrice', type: 'uint256' },
{ indexed: false, internalType: 'uint256', name: 'smoothingFactor', type: 'uint256' },
],
name: 'PairCreated',
type: 'event',
},
{
inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
name: 'allPairs',
outputs: [{ internalType: 'contract FixedPriceLiquidationPair', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'contract ILiquidationSource', name: '_source', type: 'address' },
{ internalType: 'address', name: '_tokenIn', type: 'address' },
{ internalType: 'address', name: '_tokenOut', type: 'address' },
{ internalType: 'uint256', name: '_targetAuctionPrice', type: 'uint256' },
{ internalType: 'uint256', name: '_smoothingFactor', type: 'uint256' },
],
name: 'createPair',
outputs: [{ internalType: 'contract FixedPriceLiquidationPair', name: '', type: 'address' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'pair', type: 'address' }],
name: 'deployedPairs',
outputs: [{ internalType: 'bool', name: 'wasDeployed', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'totalPairs',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
];
109 changes: 109 additions & 0 deletions packages/library/src/abis/FixedPriceLiquidationRouterAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
export const FixedPriceLiquidationRouterAbi = [
{
inputs: [
{
internalType: 'contract FixedPriceLiquidationPairFactory',
name: 'liquidationPairFactory_',
type: 'address',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [{ internalType: 'address', name: 'target', type: 'address' }],
name: 'AddressEmptyCode',
type: 'error',
},
{ inputs: [], name: 'FailedCall', type: 'error' },
{
inputs: [
{ internalType: 'uint256', name: 'balance', type: 'uint256' },
{ internalType: 'uint256', name: 'needed', type: 'uint256' },
],
name: 'InsufficientBalance',
type: 'error',
},
{
inputs: [{ internalType: 'address', name: 'sender', type: 'address' }],
name: 'InvalidSender',
type: 'error',
},
{
inputs: [{ internalType: 'address', name: 'token', type: 'address' }],
name: 'SafeERC20FailedOperation',
type: 'error',
},
{
inputs: [{ internalType: 'uint256', name: 'deadline', type: 'uint256' }],
name: 'SwapExpired',
type: 'error',
},
{ inputs: [], name: 'UndefinedFixedPriceLiquidationPairFactory', type: 'error' },
{
inputs: [{ internalType: 'address', name: 'liquidationPair', type: 'address' }],
name: 'UnknownFixedPriceLiquidationPair',
type: 'error',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'contract FixedPriceLiquidationPairFactory',
name: 'liquidationPairFactory',
type: 'address',
},
],
name: 'LiquidationRouterCreated',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'contract FixedPriceLiquidationPair',
name: 'liquidationPair',
type: 'address',
},
{ indexed: true, internalType: 'address', name: 'sender', type: 'address' },
{ indexed: true, internalType: 'address', name: 'receiver', type: 'address' },
{ indexed: false, internalType: 'uint256', name: 'amountOut', type: 'uint256' },
{ indexed: false, internalType: 'uint256', name: 'amountInMax', type: 'uint256' },
{ indexed: false, internalType: 'uint256', name: 'amountIn', type: 'uint256' },
{ indexed: false, internalType: 'uint256', name: 'deadline', type: 'uint256' },
],
name: 'SwappedExactAmountOut',
type: 'event',
},
{
inputs: [
{ internalType: 'address', name: '_sender', type: 'address' },
{ internalType: 'uint256', name: '_amountIn', type: 'uint256' },
{ internalType: 'uint256', name: '', type: 'uint256' },
{ internalType: 'bytes', name: '_flashSwapData', type: 'bytes' },
],
name: 'flashSwapCallback',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'contract FixedPriceLiquidationPair',
name: '_liquidationPair',
type: 'address',
},
{ internalType: 'address', name: '_receiver', type: 'address' },
{ internalType: 'uint256', name: '_amountOut', type: 'uint256' },
{ internalType: 'uint256', name: '_amountInMax', type: 'uint256' },
{ internalType: 'uint256', name: '_deadline', type: 'uint256' },
],
name: 'swapExactAmountOut',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
];
142 changes: 109 additions & 33 deletions packages/library/src/liquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PopulatedTransaction } from '@ethersproject/contracts';
import { ContractsBlob, getContract } from '@generationsoftware/pt-v5-utils-js';
import chalk from 'chalk';

import { LiquidatorConfig, LiquidatorContext } from './types';
import { LiquidationPair, LiquidatorConfig, LiquidatorContext } from './types';
import {
printDateTimeStr,
logTable,
Expand All @@ -20,9 +20,12 @@ import {
checkOrX,
findRecipient,
} from './utils/index.js';
import { FixedPriceLiquidationPairFactoryAbi } from './abis/FixedPriceLiquidationPairFactoryAbi.js';
import { FixedPriceLiquidationRouterAbi } from './abis/FixedPriceLiquidationRouterAbi.js';
import { ERC20Abi } from './abis/ERC20Abi.js';
import { UniswapV2WethPairFlashLiquidatorAbi } from './abis/UniswapV2WethPairFlashLiquidatorAbi.js';
import {
CHAIN_IDS,
UNISWAP_V2_WETH_PAIR_FLASH_LIQUIDATOR_CONTRACT_ADDRESS,
NETWORK_NATIVE_TOKEN_INFO,
} from './constants/index.js';
Expand Down Expand Up @@ -54,6 +57,13 @@ const stats: Stat[] = [];

const LIQUIDATOR_GAS_LIMIT: number = 1200000 as const;

export const FIXED_PRICE_CONTRACT_ADDRESS = {
[CHAIN_IDS.mainnet]: {
factory: '0xa1739ece7a90243443543ea57eb5bfb5f4f8e606',
router: '0x91b718f250a74ad80da828d7d60b13993275d43c',
},
};

const getPairName = (context: LiquidatorContext) => {
return `${context.tokenIn.symbol}/${context.tokenOut.symbol}`;
};
Expand Down Expand Up @@ -215,28 +225,21 @@ export async function runLiquidator(
contracts: ContractsBlob,
config: LiquidatorConfig,
): Promise<void> {
const { provider, relayerAddress, covalentApiKey, pairsToLiquidate } = config;
const { provider, chainId, minProfitThresholdUsd, envTokenAllowList, pairsToLiquidate, signer } =
config;
printDateTimeStr('START');
printSpacer();

const swapRecipient = findRecipient(config);

console.log(
chalk.dim('Config - MIN_PROFIT_THRESHOLD_USD:'),
chalk.yellowBright(config.minProfitThresholdUsd),
chalk.yellowBright(minProfitThresholdUsd),
);

if (config.envTokenAllowList?.length > 0) {
console.log(
chalk.dim('Config - ENV_TOKEN_ALLOW_LIST:'),
chalk.yellowBright(config.envTokenAllowList),
);
if (envTokenAllowList?.length > 0) {
console.log(chalk.dim('Config - ENV_TOKEN_ALLOW_LIST:'), chalk.yellowBright(envTokenAllowList));
}
if (config.pairsToLiquidate?.length > 0) {
console.log(
chalk.dim('Config - PAIRS_TO_LIQUIDATE:'),
chalk.yellowBright(config.pairsToLiquidate),
);
if (pairsToLiquidate?.length > 0) {
console.log(chalk.dim('Config - PAIRS_TO_LIQUIDATE:'), chalk.yellowBright(pairsToLiquidate));
}

// 1. Get contracts
Expand All @@ -251,12 +254,101 @@ export async function runLiquidator(

console.log(chalk.dim('Collecting information about vaults ...'));

// 2. Loop through all liquidation pairs
// 2. Loop through all liquidation pairs found via `contracts.json`
printSpacer();
console.log(
chalk.white.bgBlack(` # of Liquidation Pairs (RPC): ${liquidationPairContracts.length} `),
);

await loopLiquidationPairs(
config,
swapRecipient,
liquidationPairContracts,
liquidationRouterContract,
uniswapV2WethPairFlashLiquidatorContract,
);

// 3. Loop through all fixed price liquidation pairs used to liquidate yield from v3 & v4
const { fixedPriceLiquidationRouterContract, fixedPriceLiquidationPairContracts } =
await getFixedPriceLiquidationContracts(config, contracts);
if (fixedPriceLiquidationPairContracts.length > 0) {
printSpacer();
console.log(
chalk.white.bgBlack(
` # of Fixed Price Liquidation Pairs (RPC): ${liquidationPairContracts.length} `,
),
);
await loopLiquidationPairs(
config,
swapRecipient,
fixedPriceLiquidationPairContracts,
fixedPriceLiquidationRouterContract,
uniswapV2WethPairFlashLiquidatorContract,
);
}

// 4. Run Summary of all potential trades
printSpacer();
printSpacer();
console.log(chalk.greenBright.bold(`SUMMARY`));
console.table(stats);
const estimatedProfitUsdTotal = stats.reduce((accumulator, stat) => {
return accumulator + stat.estimatedProfitUsd;
}, 0);
console.log(
chalk.greenBright.bold(`ESTIMATED PROFIT: $${roundTwoDecimalPlaces(estimatedProfitUsdTotal)}`),
);

printSpacer();
printDateTimeStr('END');
printSpacer();
}

const getFixedPriceLiquidationContracts = async (
config: LiquidatorConfig,
contracts: ContractsBlob,
): Promise<{
fixedPriceLiquidationRouterContract: Contract;
fixedPriceLiquidationPairContracts: Contract[];
}> => {
const { chainId, signer, provider } = config;

let fixedPriceLiquidationRouterContract: Contract;
let fixedPriceLiquidationPairContracts: Contract[] = [];
if (FIXED_PRICE_CONTRACT_ADDRESS[chainId] !== undefined) {
const fixedPriceLiquidationPairFactoryContract = new ethers.Contract(
FIXED_PRICE_CONTRACT_ADDRESS[chainId].factory,
FixedPriceLiquidationPairFactoryAbi,
signer,
);

fixedPriceLiquidationRouterContract = new ethers.Contract(
FIXED_PRICE_CONTRACT_ADDRESS[chainId].router,
FixedPriceLiquidationRouterAbi,
signer,
);
fixedPriceLiquidationPairContracts = await getLiquidationPairsMulticall(
fixedPriceLiquidationPairFactoryContract,
contracts,
provider,
);
}

return {
fixedPriceLiquidationRouterContract,
fixedPriceLiquidationPairContracts,
};
};

const loopLiquidationPairs = async (
config: LiquidatorConfig,
swapRecipient: string,
liquidationPairContracts: Contract[],
liquidationRouterContract: Contract,
uniswapV2WethPairFlashLiquidatorContract: Contract,
) => {
const { provider, relayerAddress, covalentApiKey, pairsToLiquidate } = config;

for (let i = 0; i < liquidationPairContracts.length; i++) {
const liquidationPairContract = liquidationPairContracts[i];
if (ignorePair(liquidationPairContract.address, pairsToLiquidate)) {
Expand Down Expand Up @@ -330,23 +422,7 @@ export async function runLiquidator(
);
}
}

// 5. Run Summary of all trades and potential trades
printSpacer();
printSpacer();
console.log(chalk.greenBright.bold(`SUMMARY`));
console.table(stats);
const estimatedProfitUsdTotal = stats.reduce((accumulator, stat) => {
return accumulator + stat.estimatedProfitUsd;
}, 0);
console.log(
chalk.greenBright.bold(`ESTIMATED PROFIT: $${roundTwoDecimalPlaces(estimatedProfitUsdTotal)}`),
);

printSpacer();
printDateTimeStr('END');
printSpacer();
}
};

const processUniV2WethLPPair = async (
config: LiquidatorConfig,
Expand Down

0 comments on commit 1cd1da9

Please sign in to comment.