1- import type { CreditAccountData } from "@gearbox-protocol/sdk" ;
2- import type { OptimisticResult } from "@gearbox-protocol/types/optimist" ;
3- import type { Hex } from "viem" ;
1+ import {
2+ type CreditAccountData ,
3+ filterDustUSD ,
4+ type MultiCall ,
5+ } from "@gearbox-protocol/sdk" ;
6+ import type {
7+ OptimisticResult ,
8+ PartialLiquidationCondition ,
9+ } from "@gearbox-protocol/types/optimist" ;
10+ import type { Hex , TransactionReceipt } from "viem" ;
411import type {
512 CommonSchema ,
613 FullLiquidatorSchema ,
714 PartialLiquidatorSchema ,
815} from "../../config/index.js" ;
16+ import { TransactionRevertedError } from "../../errors/index.js" ;
917import { LoggerFactory } from "../../log/index.js" ;
1018import {
1119 LiquidationErrorMessage ,
@@ -15,7 +23,17 @@ import {
1523import AbstractLiquidator from "./AbstractLiquidator.js" ;
1624import LiquidationStrategyFull from "./LiquidationStrategyFull.js" ;
1725import LiquidationStrategyPartial from "./LiquidationStrategyPartial.js" ;
18- import type { ILiquidationStrategy , ILiquidatorService } from "./types.js" ;
26+ import type {
27+ ILiquidationStrategy ,
28+ ILiquidatorService ,
29+ LiquidationPreview ,
30+ } from "./types.js" ;
31+
32+ type OptimisticStrategyResult = {
33+ preview ?: LiquidationPreview ;
34+ partialLiquidationCondition ?: PartialLiquidationCondition < bigint > ;
35+ receipt ?: TransactionReceipt ;
36+ } & ( { success : true } | { success : false ; error : Error } ) ;
1937
2038export default class SingularLiquidator
2139 extends AbstractLiquidator < CommonSchema >
@@ -188,71 +206,117 @@ export default class SingularLiquidator
188206 } ,
189207 "liquidating account" ,
190208 ) ;
191- let snapshotId : Hex | undefined ;
192- let result = this . newOptimisticResult ( acc ) ;
209+ const result = this . newOptimisticResult ( acc ) ;
193210 const start = Date . now ( ) ;
194- try {
195- const balanceBefore = await this . getExecutorBalance ( acc . underlying ) ;
196- const mlRes = await this . #optimisticStrategy. makeLiquidatable ( acc ) ;
197- snapshotId = mlRes . snapshotId ;
198- result . partialLiquidationCondition = mlRes . partialLiquidationCondition ;
199- this . logger . debug ( { snapshotId } , "previewing..." ) ;
200- const preview = await this . #optimisticStrategy. preview ( acc ) ;
201- result = this . updateAfterPreview ( result , preview ) ;
202- this . logger . debug ( { pathHuman : result . callsHuman } , "path found" ) ;
203-
204- const { request } = await this . #optimisticStrategy. simulate ( acc , preview ) ;
211+ let strategyResult : OptimisticStrategyResult | undefined ;
205212
206- // snapshotId might be present if we had to setup liquidation conditions for single account
207- // otherwise, not write requests has been made up to this point, and it's safe to take snapshot now
208- if ( ! snapshotId ) {
209- snapshotId = await this . client . anvil . snapshot ( ) ;
213+ const balanceBefore = await this . getExecutorBalance ( acc . underlying ) ;
214+ for ( const s of this . #strategies) {
215+ if ( ! s . isApplicable ( acc ) ) {
216+ this . logger . debug ( `strategy ${ s . name } is not applicable` ) ;
217+ continue ;
210218 }
211- // ------ Actual liquidation (write request start here) -----
212- const receipt = await this . client . liquidate ( request ) ;
213- this . logger . debug ( `Liquidation tx hash: ${ receipt . transactionHash } ` ) ;
214- result . isError = receipt . status === "reverted" ;
215- this . logger . debug (
216- `Liquidation tx receipt: status=${ receipt . status } , gas=${ receipt . cumulativeGasUsed . toString ( ) } ` ,
217- ) ;
218- // ------ End of actual liquidation
219- result = await this . updateAfterLiquidation (
220- result ,
221- acc ,
222- balanceBefore . underlying ,
223- receipt ,
219+ const strategyResult = await this . #liquidateOneOptimisticStrategy( acc , s ) ;
220+ if ( strategyResult . success ) {
221+ break ;
222+ }
223+ }
224+
225+ if ( strategyResult ) {
226+ result . partialLiquidationCondition =
227+ strategyResult . partialLiquidationCondition ;
228+ result . assetOut = strategyResult . preview ?. assetOut ;
229+ result . amountOut = strategyResult . preview ?. amountOut ;
230+ result . flashLoanAmount = strategyResult . preview ?. flashLoanAmount ;
231+ result . calls = strategyResult . preview ?. calls as MultiCall [ ] ;
232+ result . pathAmount = strategyResult . preview ?. underlyingBalance ?? 0n ;
233+ result . callsHuman = this . creditAccountService . sdk . parseMultiCall ( [
234+ ...( strategyResult . preview ?. calls ?? [ ] ) ,
235+ ] ) ;
236+ const ca = await this . creditAccountService . getCreditAccountData (
237+ acc . creditAccount ,
224238 ) ;
225- // swap underlying back to ETH
239+ if ( ! ca ) {
240+ throw new Error ( `account ${ acc . creditAccount } not found` ) ;
241+ }
242+ result . balancesAfter = filterDustUSD ( { account : ca , sdk : this . sdk } ) ;
243+ result . hfAfter = ca . healthFactor ;
244+ result . gasUsed = strategyResult . receipt ?. gasUsed ?? 0n ;
245+ result . isError = ! strategyResult . success ;
246+
226247 await this . swapper . swap (
227248 acc . underlying ,
228- balanceBefore . underlying + BigInt ( result . liquidatorPremium ) ,
249+ balanceBefore . underlying + result . liquidatorPremium ,
229250 ) ;
230251 const balanceAfter = await this . getExecutorBalance ( acc . underlying ) ;
252+ result . liquidatorPremium =
253+ balanceAfter . underlying - balanceBefore . underlying ;
231254 result . liquidatorProfit = balanceAfter . eth - balanceBefore . eth ;
232- } catch ( e : any ) {
233- const decoded = await this . errorHandler . explain ( e , acc , true ) ;
234- result . traceFile = decoded . traceFile ;
235- result . error = `cannot liquidate: ${ decoded . longMessage } ` . replaceAll (
236- "\n" ,
237- "\\n" ,
238- ) ;
239- this . logger . error ( `cannot liquidate: ${ decoded . shortMessage } ` ) ;
255+
256+ if ( strategyResult ?. success === false ) {
257+ const decoded = await this . errorHandler . explain (
258+ strategyResult . error ,
259+ acc ,
260+ true ,
261+ ) ;
262+ result . traceFile = decoded . traceFile ;
263+ result . error = `cannot liquidate: ${ decoded . longMessage } ` . replaceAll (
264+ "\n" ,
265+ "\\n" ,
266+ ) ;
267+ this . logger . error ( `cannot liquidate: ${ decoded . shortMessage } ` ) ;
268+ }
269+ } else {
270+ result . isError = true ;
271+ result . error = "no applicable strategy found" ;
272+ this . logger . error ( "no applicable strategy found" ) ;
240273 }
241274
242275 result . duration = Date . now ( ) - start ;
243276 this . optimistic . push ( result ) ;
244277
245- if ( snapshotId ) {
246- await this . client . anvil . revert ( { id : snapshotId } ) ;
247- }
248-
249278 return result ;
250279 }
251280
252- get #optimisticStrategy( ) : ILiquidationStrategy {
253- if ( this . config . optimistic && this . #strategies. length !== 1 ) {
254- throw new Error ( "optimistic mode requires exactly one strategy" ) ;
281+ async #liquidateOneOptimisticStrategy(
282+ acc : CreditAccountData ,
283+ strategy : ILiquidationStrategy ,
284+ ) : Promise < OptimisticStrategyResult > {
285+ let snapshotId : Hex | undefined ;
286+ const logger = this . logger . child ( { strategy : strategy . name } ) ;
287+ let result : OptimisticStrategyResult = { success : true } ;
288+ try {
289+ const mlRes = await strategy . makeLiquidatable ( acc ) ;
290+ snapshotId = mlRes . snapshotId ;
291+ result . partialLiquidationCondition = mlRes . partialLiquidationCondition ;
292+ logger . debug ( { snapshotId, strategy : strategy . name } , "previewing..." ) ;
293+ result . preview = await strategy . preview ( acc ) ;
294+ logger . debug ( "preview successful" ) ;
295+
296+ const { request } = await strategy . simulate ( acc , result . preview ) ;
297+ logger . debug ( "simulate successful" ) ;
298+
299+ // snapshotId might be present if we had to setup liquidation conditions for single account
300+ // otherwise, not write requests has been made up to this point, and it's safe to take snapshot now
301+ if ( ! snapshotId ) {
302+ snapshotId = await this . client . anvil . snapshot ( ) ;
303+ }
304+ // ------ Actual liquidation (write request start here) -----
305+ result . receipt = await this . client . liquidate ( request ) ;
306+ logger . debug (
307+ `Liquidation tx receipt: hash=${ result . receipt . transactionHash } , status=${ result . receipt . status } , gas=${ result . receipt . cumulativeGasUsed . toString ( ) } ` ,
308+ ) ;
309+ if ( result . receipt . status !== "success" ) {
310+ throw new TransactionRevertedError ( result . receipt ) ;
311+ }
312+ } catch ( e ) {
313+ logger . error ( e , "strategy failed" ) ;
314+ result = { ...result , success : false , error : e as Error } ;
315+ } finally {
316+ if ( snapshotId ) {
317+ await this . client . anvil . revert ( { id : snapshotId } ) ;
318+ }
255319 }
256- return this . #strategies [ 0 ] ;
320+ return result ;
257321 }
258322}
0 commit comments