-
Notifications
You must be signed in to change notification settings - Fork 2
/
bot.ts
179 lines (160 loc) · 5.4 KB
/
bot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ContractKit, newKit } from '@celo/contractkit'
import Config, { logger, Mainnet } from './config'
import Loans, { Loan } from './loans'
import Oracle from './oracle'
import { setIntervalSeq } from './utils'
import { LiquidateLoan } from '@moola-v2-liquidator/contracts/generated/LiquidateLoan'
import { CeloTokenToAddress, swapPath } from './constants'
import { TransactionReceipt } from 'web3-core'
async function getLiquidationContract(kit: ContractKit) {
let jsonFile
if (Config.chain_id === Mainnet.chainId) {
jsonFile = await import(
'@moola-v2-liquidator/contracts/deployments/celo/LiquidateLoan.json'
)
} else {
jsonFile = await import(
'@moola-v2-liquidator/contracts/deployments/alfajores/LiquidateLoan.json'
)
}
return new kit.web3.eth.Contract(<any>jsonFile.abi, jsonFile.address, {
from: kit.defaultAccount,
}) as unknown as LiquidateLoan
}
class LiquidationBot {
private static _instance: LiquidationBot
static _kit: ContractKit
private static _liquidation: LiquidateLoan
private static _intervalFunc: NodeJS.Timer | undefined
private constructor(pk: string) {
LiquidationBot._kit = newKit(Config.rpc_url)
LiquidationBot._kit.connection.addAccount(pk)
}
private static async _liquidate(loan: Loan) {
logger.info(
`LiquidationBot::_liquidate(${loan.user}): Attempting Liquidation`,
)
let receipt: TransactionReceipt | undefined
let debugData
try {
const assetToLiquidate = CeloTokenToAddress[loan.maxBorrowed.token]
const flashAmt = loan.maxBorrowed.principal.multipliedBy(
Config.max_amount_liquidate,
)
const collateral = CeloTokenToAddress[loan.maxCollateral.token]
const swapP = swapPath(collateral, assetToLiquidate)
debugData = {
assetToLiquidate,
flashAmt,
collateral,
swapP,
loan,
}
// if flash amount is more than 5 celo then use higher gas price
const gasPrice = loan.maxCollateral.priceInCelo
.dividedBy(2)
.isLessThanOrEqualTo(5)
? undefined
: '690000000'
receipt = await LiquidationBot._liquidation.methods
.executeFlashLoans(
assetToLiquidate,
flashAmt.toFixed().split('.')[0],
collateral,
loan.user,
swapP,
)
.send({ from: this._kit.defaultAccount, gasPrice })
} catch (err) {
logger.error(
`LiquidationBot::_liquidate(${loan.user}): Error while Attempting Liquidation of user ${loan.user} with HF ${loan.healthFactor}`,
)
logger.error(err)
} finally {
const logOut = {
...receipt,
debugData,
}
logger.info(
`LiquidationBot::_liquidate(${loan.user}): Attempted Liquidation of user ${loan.user} with HF ${loan.healthFactor}`,
)
if (receipt?.status === true) {
// logger.info(`\n${JSON.stringify(logOut, null, 2)}`)
logger.info(
`LiquidationBot::_liquidate(${loan.user}): Liquidation Success: ${receipt.transactionHash}`,
)
} else if (receipt?.status === false) {
logger.error(
`LiquidationBot::_liquidate(${loan.user}): ${JSON.stringify({
logOut,
receipt: receipt,
})}`,
)
} else {
logger.error(
`LiquidationBot::_liquidate(${loan.user}): ${JSON.stringify({
logOut,
receipt: null,
})}`,
)
}
}
}
private static async _runPolling() {
logger.debug('LiquidationBot::_runPolling: Starting')
const badLoans = await Loans.getUnHealthy()
badLoans.forEach((l) => setTimeout(() => LiquidationBot._liquidate(l), 0))
}
public static Initialize() {
const pk = process.env.CELO_PRIVATE_KEY
if (!pk) throw new Error('Please provide CELO_PRIVATE_KEY env variable')
LiquidationBot._kit = newKit(Config.rpc_url)
LiquidationBot._kit.connection.addAccount(pk)
Oracle.Initialize()
Loans.Initialize()
logger.info('LiquidationBot::Initialize(): Initialized')
return this._instance || (this._instance = new this(pk))
}
public static isRunning(): boolean {
return LiquidationBot._intervalFunc !== undefined
}
public static async start() {
const account = (await this._kit.connection.getAccounts())[0]
this._kit.defaultAccount = account
if (LiquidationBot.isRunning()) {
logger.info('LiquidationBot::start(): Already Running')
}
// initialised contract
LiquidationBot._liquidation = await getLiquidationContract(
LiquidationBot._kit,
)
await Oracle.start()
await Loans.start()
// to initialised the prices
await LiquidationBot._runPolling()
LiquidationBot._intervalFunc = setIntervalSeq(
LiquidationBot._runPolling,
Config.bot_polling,
)
logger.info('LiquidationBot::start(): Started')
}
public static stop() {
if (LiquidationBot._intervalFunc) {
Oracle.stop()
Loans.stop()
clearInterval(LiquidationBot._intervalFunc)
logger.info('LiquidationBot:"stop() Stopped')
} else {
logger.info('LiquidationBot:"stop() Not Running')
}
}
public static async startBlocking() {
logger.info('LiquidationBot::startBlocking(): Started')
// eslint-disable-next-line no-constant-condition
while (true) {
await LiquidationBot._runPolling()
}
}
}
export default LiquidationBot