@gearbox-protocol/sdk 17.12.2 → 17.13.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/cjs/onchain/accounts/CreditAccountsServiceV310.js +1 -1
  2. package/dist/cjs/onchain/accounts/index.js +3 -0
  3. package/dist/cjs/onchain/accounts/liquidations/LiquidationsService.js +43 -1
  4. package/dist/cjs/onchain/accounts/liquidations/index.js +3 -0
  5. package/dist/cjs/onchain/accounts/liquidations/partialLiquidation.js +74 -0
  6. package/dist/cjs/onchain/index.js +4 -1
  7. package/dist/cjs/onchain/market/credit/CreditSuite.js +0 -60
  8. package/dist/cjs/onchain/market/credit/collateralUtils.js +3 -2
  9. package/dist/cjs/onchain/market/index.js +1 -1
  10. package/dist/cjs/onchain/market/math.js +34 -12
  11. package/dist/esm/onchain/accounts/CreditAccountsServiceV310.js +1 -1
  12. package/dist/esm/onchain/accounts/index.js +2 -1
  13. package/dist/esm/onchain/accounts/liquidations/LiquidationsService.js +43 -1
  14. package/dist/esm/onchain/accounts/liquidations/index.js +2 -1
  15. package/dist/esm/onchain/accounts/liquidations/partialLiquidation.js +72 -0
  16. package/dist/esm/onchain/index.js +3 -2
  17. package/dist/esm/onchain/market/credit/CreditSuite.js +2 -62
  18. package/dist/esm/onchain/market/credit/collateralUtils.js +3 -2
  19. package/dist/esm/onchain/market/index.js +2 -2
  20. package/dist/esm/onchain/market/math.js +34 -12
  21. package/dist/types/onchain/OnchainSDK.d.ts +1 -1
  22. package/dist/types/onchain/accounts/index.d.ts +7 -6
  23. package/dist/types/onchain/accounts/liquidations/LiquidationsService.d.ts +27 -1
  24. package/dist/types/onchain/accounts/liquidations/index.d.ts +3 -2
  25. package/dist/types/onchain/accounts/liquidations/partialLiquidation.d.ts +81 -0
  26. package/dist/types/onchain/accounts/liquidations/types.d.ts +36 -1
  27. package/dist/types/onchain/accounts/types.d.ts +3 -1
  28. package/dist/types/onchain/index.d.ts +8 -7
  29. package/dist/types/onchain/market/credit/CreditSuite.d.ts +1 -11
  30. package/dist/types/onchain/market/credit/collateralUtils.d.ts +2 -1
  31. package/dist/types/onchain/market/credit/index.d.ts +2 -2
  32. package/dist/types/onchain/market/credit/types.d.ts +1 -36
  33. package/dist/types/onchain/market/index.d.ts +3 -3
  34. package/dist/types/onchain/market/math.d.ts +29 -10
  35. package/package.json +1 -1
@@ -126,7 +126,7 @@ var CreditAccountsServiceV310 = class extends require_onchain_base_SDKConstruct.
126
126
  async partiallyLiquidate(props) {
127
127
  const { account, to } = props;
128
128
  const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
129
- const { tokenOut, repaidAmount, minSeizedAmount } = cm.partialLiquidationParams(account, props);
129
+ const { tokenOut, repaidAmount, minSeizedAmount } = this.sdk.liquidations.getPartialLiquidationParams(account, props);
130
130
  const updates = await this.getOnDemandPriceUpdates(account, true);
131
131
  return cm.creditFacade.partiallyLiquidateCreditAccount(account.creditAccount, tokenOut, repaidAmount, minSeizedAmount, to, updates);
132
132
  }
@@ -11,6 +11,7 @@ const require_onchain_accounts_intents_utils_credit_account_slice = require("./i
11
11
  const require_onchain_accounts_quota_utils = require("./quota-utils.js");
12
12
  const require_onchain_accounts_intents_index = require("./intents/index.js");
13
13
  const require_onchain_accounts_liquidations_constants = require("./liquidations/constants.js");
14
+ const require_onchain_accounts_liquidations_partialLiquidation = require("./liquidations/partialLiquidation.js");
14
15
  const require_onchain_accounts_liquidations_LiquidationsService = require("./liquidations/LiquidationsService.js");
15
16
  const require_onchain_accounts_liquidations_MultichainLiquidationsService = require("./liquidations/MultichainLiquidationsService.js");
16
17
  require("./liquidations/index.js");
@@ -56,6 +57,8 @@ exports.getWithdrawalCompressorAddress = require_onchain_accounts_withdrawal_com
56
57
  exports.iCreditAccountAbi = require_onchain_accounts_withdrawal_compressor_AbstractWithdrawalCompressorContract.iCreditAccountAbi;
57
58
  exports.isPhantomToken = require_onchain_accounts_intents_utils_pick_token.isPhantomToken;
58
59
  exports.maxAmountForQuota = require_onchain_accounts_quota_utils.maxAmountForQuota;
60
+ exports.optimalPartialLiquidation = require_onchain_accounts_liquidations_partialLiquidation.optimalPartialLiquidation;
61
+ exports.partialLiquidationParams = require_onchain_accounts_liquidations_partialLiquidation.partialLiquidationParams;
59
62
  exports.roundUpQuota = require_onchain_accounts_quota_utils.roundUpQuota;
60
63
  exports.toClaimableWithdrawal = require_onchain_accounts_withdrawal_compressor_AbstractWithdrawalCompressorContract.toClaimableWithdrawal;
61
64
  exports.toCreditAccountSlice = require_onchain_accounts_intents_utils_credit_account_slice.toCreditAccountSlice;
@@ -21,6 +21,7 @@ const require_onchain_market_rwa_securitize_SecuritizeLiquidatorContract = requi
21
21
  require("../../market/rwa/securitize/index.js");
22
22
  require("../../market/index.js");
23
23
  const require_onchain_accounts_liquidations_constants = require("./constants.js");
24
+ const require_onchain_accounts_liquidations_partialLiquidation = require("./partialLiquidation.js");
24
25
  const require_onchain_accounts_liquidations_skipLiquidatableAccount = require("./skipLiquidatableAccount.js");
25
26
  //#region src/onchain/accounts/liquidations/LiquidationsService.ts
26
27
  /**
@@ -88,6 +89,44 @@ var LiquidationsService = class extends require_onchain_base_SDKConstruct.SDKCon
88
89
  };
89
90
  }
90
91
  /**
92
+ * Optimal partial liquidation of a credit account: the collateral to seize
93
+ * and the amounts that bring its health factor close to `optimalHF`.
94
+ *
95
+ * Prices come from the last synced oracle state and `ca`, not from fresh
96
+ * price updates. Withdrawal phantom tokens are never seized.
97
+ *
98
+ * @param ca - Credit account to partially liquidate.
99
+ * @param optimalHF - Health factor to aim for, in basis points. Defaults to
100
+ * {@link CreditSuite.optimalHFForPartialLiquidation}.
101
+ * @throws If no `tokenOut` can be picked.
102
+ **/
103
+ getOptimalPartialLiquidation(ca, optimalHF) {
104
+ return require_onchain_accounts_liquidations_partialLiquidation.optimalPartialLiquidation({
105
+ suite: this.sdk.marketRegister.findCreditManager(ca.creditManager),
106
+ account: ca,
107
+ optimalHF,
108
+ exclude: (token) => this.#isWithdrawalPhantom(token)
109
+ });
110
+ }
111
+ /**
112
+ * Everything a partial liquidation of a credit account needs, with any
113
+ * parameter the caller pinned down taken as given and the rest derived from
114
+ * current state. A derived `tokenOut` is never a withdrawal phantom token.
115
+ *
116
+ * @param ca - Credit account to partially liquidate.
117
+ * @param overrides - Parameters to use instead of the derived defaults.
118
+ * @throws If a derived `tokenOut` cannot be picked, or if the seized token is
119
+ * not a collateral token of the credit manager.
120
+ **/
121
+ getPartialLiquidationParams(ca, overrides) {
122
+ return require_onchain_accounts_liquidations_partialLiquidation.partialLiquidationParams({
123
+ suite: this.sdk.marketRegister.findCreditManager(ca.creditManager),
124
+ account: ca,
125
+ overrides,
126
+ exclude: (token) => this.#isWithdrawalPhantom(token)
127
+ });
128
+ }
129
+ /**
91
130
  * Returns the status of delayed-withdrawal positions (redemption receipts)
92
131
  * owned by a liquidator wallet: what is receivable, how much, and when it
93
132
  * becomes claimable.
@@ -279,11 +318,14 @@ var LiquidationsService = class extends require_onchain_base_SDKConstruct.SDKCon
279
318
  value: estimatedProfit,
280
319
  valueUsd: usd(estimatedProfit)
281
320
  },
282
- isDelayed: ca.tokens.some((t) => t.balance > 10n && !!this.sdk.withdrawalCompressor?.getWithdrawalSourceToken(t.token)),
321
+ isDelayed: ca.tokens.some((t) => t.balance > 10n && this.#isWithdrawalPhantom(t.token)),
283
322
  paused: suite.creditFacade.isPaused,
284
323
  rwa: market.rwa
285
324
  };
286
325
  }
326
+ #isWithdrawalPhantom(token) {
327
+ return !!this.sdk.withdrawalCompressor?.getWithdrawalSourceToken(token);
328
+ }
287
329
  #mainAsset(ca, market, fallback) {
288
330
  const asset = require_onchain_market_credit_collateralUtils.dominantCollateral(ca, market);
289
331
  if (!asset) return fallback;
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_onchain_accounts_liquidations_constants = require("./constants.js");
3
+ const require_onchain_accounts_liquidations_partialLiquidation = require("./partialLiquidation.js");
3
4
  const require_onchain_accounts_liquidations_LiquidationsService = require("./LiquidationsService.js");
4
5
  const require_onchain_accounts_liquidations_MultichainLiquidationsService = require("./MultichainLiquidationsService.js");
5
6
  require("./types.js");
@@ -7,3 +8,5 @@ exports.LIQUIDATION_APPROVAL_BUFFER = require_onchain_accounts_liquidations_cons
7
8
  exports.LIQUIDATION_COMPRESSOR_V313_ADDRESS = require_onchain_accounts_liquidations_constants.LIQUIDATION_COMPRESSOR_V313_ADDRESS;
8
9
  exports.LiquidationsService = require_onchain_accounts_liquidations_LiquidationsService.LiquidationsService;
9
10
  exports.MultichainLiquidationsService = require_onchain_accounts_liquidations_MultichainLiquidationsService.MultichainLiquidationsService;
11
+ exports.optimalPartialLiquidation = require_onchain_accounts_liquidations_partialLiquidation.optimalPartialLiquidation;
12
+ exports.partialLiquidationParams = require_onchain_accounts_liquidations_partialLiquidation.partialLiquidationParams;
@@ -0,0 +1,74 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_onchain_market_math = require("../../market/math.js");
3
+ const require_onchain_market_credit_collateralUtils = require("../../market/credit/collateralUtils.js");
4
+ require("../../market/index.js");
5
+ //#region src/onchain/accounts/liquidations/partialLiquidation.ts
6
+ /**
7
+ * Optimal partial liquidation of an account: the collateral to seize and the
8
+ * amounts that bring its health factor close to `optimalHF`.
9
+ *
10
+ * Ported from solidity, without the price updates the contract applies first:
11
+ * https://github.com/Gearbox-protocol/router-v3/blob/56e2d515ec6d9bb1e324e71c3708e59710779b24/contracts/liquidation/AbstractLiquidator.sol#L252
12
+ *
13
+ * @throws If no `tokenOut` can be picked.
14
+ **/
15
+ function optimalPartialLiquidation({ suite, account, optimalHF = suite.optimalHFForPartialLiquidation(account), exclude }) {
16
+ const tokenOut = bestTokenOut(suite, account, exclude);
17
+ return {
18
+ tokenOut,
19
+ ...optimalAmounts(suite, account, tokenOut, optimalHF)
20
+ };
21
+ }
22
+ /**
23
+ * Everything a partial liquidation of credit account needs, with any parameter
24
+ * the caller pinned down taken as given and the rest derived from current state.
25
+ *
26
+ * @throws If a derived `tokenOut` cannot be picked, or if the seized token is
27
+ * not a collateral token of the credit manager.
28
+ **/
29
+ function partialLiquidationParams({ suite, account, overrides = {}, exclude }) {
30
+ const tokenOut = overrides.tokenOut ?? bestTokenOut(suite, account, exclude);
31
+ const optimalHF = overrides.optimalHF ?? suite.optimalHFForPartialLiquidation(account);
32
+ const repaidAmount = overrides.repaidAmount ?? optimalAmounts(suite, account, tokenOut, optimalHF).repaidAmount;
33
+ return {
34
+ tokenOut,
35
+ optimalHF,
36
+ repaidAmount,
37
+ minSeizedAmount: overrides.minSeizedAmount ?? minSeizedAmountOf(suite, tokenOut, repaidAmount)
38
+ };
39
+ }
40
+ function bestTokenOut(suite, account, exclude) {
41
+ const collateral = require_onchain_market_credit_collateralUtils.dominantCollateral(account, suite.market, exclude);
42
+ if (!collateral) throw new Error(`cannot determine tokenOut for partial liquidation of ${suite.register.labelAddress(account.creditAccount)}: no enabled non-underlying collateral with value`);
43
+ return collateral;
44
+ }
45
+ /**
46
+ * @throws If `tokenOut` is not a collateral token of the credit manager.
47
+ **/
48
+ function optimalAmounts(suite, account, tokenOut, optimalHF) {
49
+ const { creditManager: cm, market } = suite;
50
+ const { feeLiquidation, liquidationDiscount } = suite.liquidationFees();
51
+ const ltTokenOut = cm.liquidationThresholds.get(tokenOut);
52
+ if (ltTokenOut === void 0) throw new Error(`token ${suite.register.labelAddress(tokenOut)} is not a collateral token in credit manager ${suite.register.labelAddress(cm.address)}`);
53
+ return require_onchain_market_math.optimalPartialLiquidationAmounts({
54
+ totalDebt: account.debt + account.accruedInterest + account.accruedFees,
55
+ twvUnderlying: market.priceOracle.convertFromUSD(market.underlying, account.twvUSD),
56
+ minDebt: suite.creditFacade.minDebt,
57
+ optimalHF,
58
+ liquidationDiscount: BigInt(liquidationDiscount),
59
+ discount: BigInt(liquidationDiscount) - BigInt(feeLiquidation),
60
+ ltTokenOut: BigInt(ltTokenOut)
61
+ });
62
+ }
63
+ /**
64
+ * Minimum amount of `token` that must be seized when repaying `repaidAmount`
65
+ * of underlying.
66
+ **/
67
+ function minSeizedAmountOf(suite, token, repaidAmount) {
68
+ const { market } = suite;
69
+ const tokenAmount = market.priceOracle.convert(market.underlying, token, repaidAmount);
70
+ return require_onchain_market_math.minSeizedAmount(tokenAmount, suite.liquidationFees().liquidationDiscount);
71
+ }
72
+ //#endregion
73
+ exports.optimalPartialLiquidation = optimalPartialLiquidation;
74
+ exports.partialLiquidationParams = partialLiquidationParams;
@@ -246,6 +246,7 @@ const require_onchain_accounts_intents_utils_credit_account_slice = require("./a
246
246
  const require_onchain_accounts_quota_utils = require("./accounts/quota-utils.js");
247
247
  const require_onchain_accounts_intents_index = require("./accounts/intents/index.js");
248
248
  const require_onchain_accounts_liquidations_constants = require("./accounts/liquidations/constants.js");
249
+ const require_onchain_accounts_liquidations_partialLiquidation = require("./accounts/liquidations/partialLiquidation.js");
249
250
  const require_onchain_accounts_liquidations_LiquidationsService = require("./accounts/liquidations/LiquidationsService.js");
250
251
  const require_onchain_accounts_liquidations_MultichainLiquidationsService = require("./accounts/liquidations/MultichainLiquidationsService.js");
251
252
  const require_onchain_accounts_withdrawal_compressor_intent_codec = require("./accounts/withdrawal-compressor/intent-codec.js");
@@ -773,7 +774,8 @@ exports.minSeizedAmount = require_onchain_market_math.minSeizedAmount;
773
774
  exports.numberWithCommas = require_onchain_utils_formatter.numberWithCommas;
774
775
  exports.onchainSDKOptionsSchema = require_onchain_options.onchainSDKOptionsSchema;
775
776
  exports.optimalHFForPartialLiquidation = require_onchain_market_math.optimalHFForPartialLiquidation;
776
- exports.optimalRepaidAmount = require_onchain_market_math.optimalRepaidAmount;
777
+ exports.optimalPartialLiquidation = require_onchain_accounts_liquidations_partialLiquidation.optimalPartialLiquidation;
778
+ exports.optimalPartialLiquidationAmounts = require_onchain_market_math.optimalPartialLiquidationAmounts;
777
779
  exports.parseAdapterAction = require_onchain_market_adapters_abi_utils.parseAdapterAction;
778
780
  exports.parseAdapterDeployParams = require_onchain_market_adapters_abi_utils.parseAdapterDeployParams;
779
781
  exports.parseFacadeOperationCalldata = require_onchain_preview_parse_parseFacadeOperationCalldata.parseFacadeOperationCalldata;
@@ -781,6 +783,7 @@ exports.parseOperationCalldata = require_onchain_preview_parse_parseOperationCal
781
783
  exports.parsePoolOperationCalldata = require_onchain_preview_parse_parsePoolOperationCalldata.parsePoolOperationCalldata;
782
784
  exports.parsePosNegAmount = require_onchain_market_adapters_transferHelpers.parsePosNegAmount;
783
785
  exports.parseRWAFactoryOperationCalldata = require_onchain_preview_parse_parseRWAFactoryOperationCalldata.parseRWAFactoryOperationCalldata;
786
+ exports.partialLiquidationParams = require_onchain_accounts_liquidations_partialLiquidation.partialLiquidationParams;
784
787
  exports.percentFmt = require_onchain_utils_formatter.percentFmt;
785
788
  exports.pickStrategyTargetCollateral = require_onchain_market_credit_collateralUtils.pickStrategyTargetCollateral;
786
789
  exports.previewAdjustStrategyPosition = require_onchain_preview_preview_previewAdjustStrategyPosition.previewAdjustStrategyPosition;
@@ -335,26 +335,6 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
335
335
  return target ? require_onchain_market_strategyName.strategyName(target, this.underlyingToken) : this.underlyingToken.symbol;
336
336
  }
337
337
  /**
338
- * Everything a partial liquidation of credit account needs, with any parameter the
339
- * caller pinned down taken as given and the rest derived from current state.
340
- *
341
- * @param ca - Credit account to partially liquidate.
342
- * @param overrides - Parameters to use instead of the derived defaults.
343
- * @throws If a derived `tokenOut` cannot be picked, or if the seized token is
344
- * not a collateral token of this credit manager.
345
- */
346
- partialLiquidationParams(ca, overrides = {}) {
347
- const tokenOut = overrides.tokenOut ?? this.#bestTokenOut(ca);
348
- const optimalHF = overrides.optimalHF ?? this.optimalHFForPartialLiquidation(ca);
349
- const repaidAmount = overrides.repaidAmount ?? this.#optimalRepaidAmount(ca, tokenOut, optimalHF);
350
- return {
351
- tokenOut,
352
- optimalHF,
353
- repaidAmount,
354
- minSeizedAmount: overrides.minSeizedAmount ?? this.#minSeizedAmount(tokenOut, repaidAmount)
355
- };
356
- }
357
- /**
358
338
  * Health factor a partial liquidation of `ca` should target, in basis points.
359
339
  *
360
340
  * @param ca - Credit account to partially liquidate.
@@ -363,17 +343,6 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
363
343
  return require_onchain_market_math.optimalHFForPartialLiquidation(this.#borrowRate(ca));
364
344
  }
365
345
  /**
366
- * Collateral token a partial liquidation seizes by default.
367
- *
368
- * Ported from solidity:
369
- * https://github.com/Gearbox-protocol/router-v3/blob/main/contracts/liquidation/AbstractLiquidator.sol#L270
370
- */
371
- #bestTokenOut(ca) {
372
- const collateral = require_onchain_market_credit_collateralUtils.dominantCollateral(ca, this.market);
373
- if (!collateral) throw new Error(`cannot determine tokenOut for partial liquidation of ${this.labelAddress(ca.creditAccount)}: no enabled non-underlying collateral with value`);
374
- return collateral;
375
- }
376
- /**
377
346
  * Shared inputs of {@link isStrategyCollateral} for one of this suite's
378
347
  * collateral tokens.
379
348
  */
@@ -391,35 +360,6 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
391
360
  };
392
361
  }
393
362
  /**
394
- * Minimum amount of `token` that must be seized when repaying `repaidAmount`
395
- * of underlying.
396
- */
397
- #minSeizedAmount(token, repaidAmount) {
398
- const { market } = this;
399
- const tokenAmount = market.priceOracle.convert(market.underlying, token, repaidAmount);
400
- return require_onchain_market_math.minSeizedAmount(tokenAmount, this.liquidationFees().liquidationDiscount);
401
- }
402
- /**
403
- * Amount of underlying to repay to bring `ca`'s health factor close to
404
- * `optimalHF` by seizing `token`.
405
- *
406
- * @throws If `token` is not a collateral token of this credit manager.
407
- */
408
- #optimalRepaidAmount(ca, token, optimalHF) {
409
- const { creditManager: cm, market } = this;
410
- const { feeLiquidation, liquidationDiscount } = this.liquidationFees();
411
- const ltTokenOut = cm.liquidationThresholds.get(token);
412
- if (ltTokenOut === void 0) throw new Error(`token ${this.labelAddress(token)} is not a collateral token in credit manager ${this.labelAddress(cm.address)}`);
413
- return require_onchain_market_math.optimalRepaidAmount({
414
- totalDebt: ca.debt + ca.accruedInterest + ca.accruedFees,
415
- twvUnderlying: market.priceOracle.convertFromUSD(market.underlying, ca.twvUSD),
416
- minDebt: this.creditFacade.minDebt,
417
- optimalHF,
418
- discount: BigInt(liquidationDiscount) - BigInt(feeLiquidation),
419
- ltTokenOut: BigInt(ltTokenOut)
420
- });
421
- }
422
- /**
423
363
  * Blended annual cost of credit account's debt, in basis points: base interest weighted
424
364
  * by the account's share of its own total debt, plus the quota rates of the
425
365
  * collaterals it actually holds, both marked up by the interest fee.
@@ -63,14 +63,15 @@ function pickStrategyTargetCollateral(tokens) {
63
63
  *
64
64
  * @param account - Account to inspect.
65
65
  * @param market - Market of the account, whose oracle prices the candidates.
66
+ * @param exclude - Tokens that cannot be the dominant collateral.
66
67
  * @returns The dominant collateral, or `undefined` when the account holds
67
68
  * nothing but its underlying, or nothing the oracle can price.
68
69
  **/
69
- function dominantCollateral(account, market) {
70
+ function dominantCollateral(account, market, exclude) {
70
71
  let bestValue = 0n;
71
72
  let dominant;
72
73
  for (const t of account.tokens) {
73
- if ((0, viem.isAddressEqual)(t.token, account.underlying) || (t.mask & account.enabledTokensMask) === 0n || t.balance <= 10n) continue;
74
+ if ((0, viem.isAddressEqual)(t.token, account.underlying) || (t.mask & account.enabledTokensMask) === 0n || t.balance <= 10n || exclude?.(t.token)) continue;
74
75
  const value = market.priceOracle.safeConvertToUSD(t.token, t.balance).value;
75
76
  if (value > bestValue) {
76
77
  bestValue = value;
@@ -360,7 +360,7 @@ exports.lidoV1_WETHGatewayAbi = require_onchain_market_adapters_abi_targetContra
360
360
  exports.mellowDvvAdapterAbi = require_onchain_market_adapters_abi_adapters_mellowDvvAdapter.mellowDvvAdapterAbi;
361
361
  exports.minSeizedAmount = require_onchain_market_math.minSeizedAmount;
362
362
  exports.optimalHFForPartialLiquidation = require_onchain_market_math.optimalHFForPartialLiquidation;
363
- exports.optimalRepaidAmount = require_onchain_market_math.optimalRepaidAmount;
363
+ exports.optimalPartialLiquidationAmounts = require_onchain_market_math.optimalPartialLiquidationAmounts;
364
364
  exports.parseAdapterAction = require_onchain_market_adapters_abi_utils.parseAdapterAction;
365
365
  exports.parseAdapterDeployParams = require_onchain_market_adapters_abi_utils.parseAdapterDeployParams;
366
366
  exports.parsePosNegAmount = require_onchain_market_adapters_transferHelpers.parsePosNegAmount;
@@ -257,28 +257,50 @@ function minSeizedAmount(tokenAmount, liquidationDiscount) {
257
257
  return tokenAmount * PARTIAL_LIQUIDATION_BUFFER_BPS / BigInt(liquidationDiscount);
258
258
  }
259
259
  /**
260
- * Amount of underlying whose repayment brings the account's health factor close
261
- * to `optimalHF`, capped so the account keeps at least `minDebt` of debt.
260
+ * Amounts of a partial liquidation that brings the account's health factor
261
+ * close to `optimalHF`, capped so the account keeps at least `minDebt` of debt.
262
262
  *
263
263
  * Ported from solidity:
264
- * https://github.com/Gearbox-protocol/router-v3/blob/56e2d515ec6d9bb1e324e71c3708e59710779b24/contracts/liquidation/AbstractLiquidator.sol#L292
264
+ * https://github.com/Gearbox-protocol/router-v3/blob/56e2d515ec6d9bb1e324e71c3708e59710779b24/contracts/liquidation/AbstractLiquidator.sol#L252
265
265
  *
266
- * @returns The repaid amount, or `0n` when the account is already healthy
267
- * enough or carries less than the minimum debt.
266
+ * @returns Zero amounts when the account is already healthy enough or
267
+ * carries less than the minimum debt.
268
268
  * @throws If the discounted target health factor does not exceed the seized
269
269
  * token's liquidation threshold, in which case no repayment improves the
270
270
  * account.
271
271
  **/
272
- function optimalRepaidAmount({ totalDebt, twvUnderlying, minDebt, optimalHF, discount, ltTokenOut }) {
272
+ function optimalPartialLiquidationAmounts({ totalDebt, twvUnderlying, minDebt, optimalHF, liquidationDiscount, discount, ltTokenOut }) {
273
273
  const denominator = discount * optimalHF / require_onchain_constants_math.PERCENTAGE_FACTOR - ltTokenOut;
274
274
  if (denominator <= 0n) throw new Error("cannot compute optimal repaid amount: invalid liquidation parameters (discount * hfOptimal <= ltTokenOut)");
275
275
  const numerator = totalDebt * optimalHF - twvUnderlying * require_onchain_constants_math.PERCENTAGE_FACTOR;
276
- if (numerator <= 0n) return 0n;
277
- const repaidAmount = numerator / denominator * discount / require_onchain_constants_math.PERCENTAGE_FACTOR;
278
- if (totalDebt < minDebt) return 0n;
276
+ if (numerator <= 0n) return {
277
+ optimalAmount: 0n,
278
+ repaidAmount: 0n,
279
+ flashLoanAmount: 0n,
280
+ isOptimalRepayable: true
281
+ };
282
+ if (totalDebt < minDebt) return {
283
+ optimalAmount: 0n,
284
+ repaidAmount: 0n,
285
+ flashLoanAmount: 0n,
286
+ isOptimalRepayable: false
287
+ };
288
+ const optimalValueSeized = numerator / denominator;
289
+ let optimalAmount = optimalValueSeized * liquidationDiscount / require_onchain_constants_math.PERCENTAGE_FACTOR;
290
+ let repaidAmount = optimalValueSeized * discount / require_onchain_constants_math.PERCENTAGE_FACTOR;
291
+ let isOptimalRepayable = true;
279
292
  const surplusDebt = totalDebt - minDebt;
280
- if (repaidAmount > surplusDebt) return surplusDebt * PARTIAL_LIQUIDATION_BUFFER_BPS / require_onchain_constants_math.PERCENTAGE_FACTOR;
281
- return repaidAmount;
293
+ if (repaidAmount > surplusDebt) {
294
+ optimalAmount = optimalAmount * surplusDebt * PARTIAL_LIQUIDATION_BUFFER_BPS / (repaidAmount * require_onchain_constants_math.PERCENTAGE_FACTOR);
295
+ repaidAmount = surplusDebt * PARTIAL_LIQUIDATION_BUFFER_BPS / require_onchain_constants_math.PERCENTAGE_FACTOR;
296
+ isOptimalRepayable = false;
297
+ }
298
+ return {
299
+ optimalAmount,
300
+ repaidAmount,
301
+ flashLoanAmount: optimalAmount * 1001n / 1000n,
302
+ isOptimalRepayable
303
+ };
282
304
  }
283
305
  /**
284
306
  * Health factor a partial liquidation should target, in basis points: just
@@ -306,6 +328,6 @@ exports.calcUtilizationRaw = calcUtilizationRaw;
306
328
  exports.healthFactorBps = healthFactorBps;
307
329
  exports.minSeizedAmount = minSeizedAmount;
308
330
  exports.optimalHFForPartialLiquidation = optimalHFForPartialLiquidation;
309
- exports.optimalRepaidAmount = optimalRepaidAmount;
331
+ exports.optimalPartialLiquidationAmounts = optimalPartialLiquidationAmounts;
310
332
  exports.rayToBps = rayToBps;
311
333
  exports.usdToNumber = usdToNumber;
@@ -125,7 +125,7 @@ var CreditAccountsServiceV310 = class extends SDKConstruct {
125
125
  async partiallyLiquidate(props) {
126
126
  const { account, to } = props;
127
127
  const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
128
- const { tokenOut, repaidAmount, minSeizedAmount } = cm.partialLiquidationParams(account, props);
128
+ const { tokenOut, repaidAmount, minSeizedAmount } = this.sdk.liquidations.getPartialLiquidationParams(account, props);
129
129
  const updates = await this.getOnDemandPriceUpdates(account, true);
130
130
  return cm.creditFacade.partiallyLiquidateCreditAccount(account.creditAccount, tokenOut, repaidAmount, minSeizedAmount, to, updates);
131
131
  }
@@ -10,6 +10,7 @@ import { fetchCreditAccountSlice, toCreditAccountSlice } from "./intents/utils/c
10
10
  import { DIRECT_TRANSFERS_QUOTA, calcDefaultQuota, calcQuotaUpdate, calcRecommendedQuota, maxAmountForQuota, roundUpQuota } from "./quota-utils.js";
11
11
  import { CreditAccountOperationsService } from "./intents/index.js";
12
12
  import { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS } from "./liquidations/constants.js";
13
+ import { optimalPartialLiquidation, partialLiquidationParams } from "./liquidations/partialLiquidation.js";
13
14
  import { LiquidationsService } from "./liquidations/LiquidationsService.js";
14
15
  import { MultichainLiquidationsService } from "./liquidations/MultichainLiquidationsService.js";
15
16
  import "./liquidations/index.js";
@@ -25,4 +26,4 @@ import { toWithdrawalStatus } from "./withdrawal-compressor/types.js";
25
26
  import { WithdrawalCompressorV313Contract } from "./withdrawal-compressor/WithdrawalCompressorV313Contract.js";
26
27
  import { createWithdrawalCompressor } from "./withdrawal-compressor/createWithdrawalCompressor.js";
27
28
  import "./withdrawal-compressor/index.js";
28
- export { AbstractWithdrawalCompressorContract, AccountBotsService, CreditAccountCompressor, CreditAccountCompressorV310Contract, CreditAccountOperationsService, CreditAccountsServiceV310, DELAYED_INTENT_TYPES, DELAYED_INTENT_VERSION, DIRECT_TRANSFERS_QUOTA, LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService, PeripheryCompressorV310Contract, RedemptionLoggerV310Contract, WithdrawalCompressorV310Contract, WithdrawalCompressorV311Contract, WithdrawalCompressorV313Contract, calcDefaultQuota, calcQuotaUpdate, calcRecommendedQuota, createRedemptionLogger, createWithdrawalCompressor, decodeDelayedIntent, encodeDelayedIntent, fetchCreditAccountSlice, getWithdrawalCompressorAddress, iCreditAccountAbi, isPhantomToken, maxAmountForQuota, roundUpQuota, toClaimableWithdrawal, toCreditAccountSlice, toPendingWithdrawal, toRequestableWithdrawal, toWithdrawalStatus };
29
+ export { AbstractWithdrawalCompressorContract, AccountBotsService, CreditAccountCompressor, CreditAccountCompressorV310Contract, CreditAccountOperationsService, CreditAccountsServiceV310, DELAYED_INTENT_TYPES, DELAYED_INTENT_VERSION, DIRECT_TRANSFERS_QUOTA, LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService, PeripheryCompressorV310Contract, RedemptionLoggerV310Contract, WithdrawalCompressorV310Contract, WithdrawalCompressorV311Contract, WithdrawalCompressorV313Contract, calcDefaultQuota, calcQuotaUpdate, calcRecommendedQuota, createRedemptionLogger, createWithdrawalCompressor, decodeDelayedIntent, encodeDelayedIntent, fetchCreditAccountSlice, getWithdrawalCompressorAddress, iCreditAccountAbi, isPhantomToken, maxAmountForQuota, optimalPartialLiquidation, partialLiquidationParams, roundUpQuota, toClaimableWithdrawal, toCreditAccountSlice, toPendingWithdrawal, toRequestableWithdrawal, toWithdrawalStatus };
@@ -20,6 +20,7 @@ import { SecuritizeLiquidatorContract } from "../../market/rwa/securitize/Securi
20
20
  import "../../market/rwa/securitize/index.js";
21
21
  import "../../market/index.js";
22
22
  import { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS } from "./constants.js";
23
+ import { optimalPartialLiquidation, partialLiquidationParams } from "./partialLiquidation.js";
23
24
  import { skipLiquidatableAccount } from "./skipLiquidatableAccount.js";
24
25
  //#region src/onchain/accounts/liquidations/LiquidationsService.ts
25
26
  /**
@@ -87,6 +88,44 @@ var LiquidationsService = class extends SDKConstruct {
87
88
  };
88
89
  }
89
90
  /**
91
+ * Optimal partial liquidation of a credit account: the collateral to seize
92
+ * and the amounts that bring its health factor close to `optimalHF`.
93
+ *
94
+ * Prices come from the last synced oracle state and `ca`, not from fresh
95
+ * price updates. Withdrawal phantom tokens are never seized.
96
+ *
97
+ * @param ca - Credit account to partially liquidate.
98
+ * @param optimalHF - Health factor to aim for, in basis points. Defaults to
99
+ * {@link CreditSuite.optimalHFForPartialLiquidation}.
100
+ * @throws If no `tokenOut` can be picked.
101
+ **/
102
+ getOptimalPartialLiquidation(ca, optimalHF) {
103
+ return optimalPartialLiquidation({
104
+ suite: this.sdk.marketRegister.findCreditManager(ca.creditManager),
105
+ account: ca,
106
+ optimalHF,
107
+ exclude: (token) => this.#isWithdrawalPhantom(token)
108
+ });
109
+ }
110
+ /**
111
+ * Everything a partial liquidation of a credit account needs, with any
112
+ * parameter the caller pinned down taken as given and the rest derived from
113
+ * current state. A derived `tokenOut` is never a withdrawal phantom token.
114
+ *
115
+ * @param ca - Credit account to partially liquidate.
116
+ * @param overrides - Parameters to use instead of the derived defaults.
117
+ * @throws If a derived `tokenOut` cannot be picked, or if the seized token is
118
+ * not a collateral token of the credit manager.
119
+ **/
120
+ getPartialLiquidationParams(ca, overrides) {
121
+ return partialLiquidationParams({
122
+ suite: this.sdk.marketRegister.findCreditManager(ca.creditManager),
123
+ account: ca,
124
+ overrides,
125
+ exclude: (token) => this.#isWithdrawalPhantom(token)
126
+ });
127
+ }
128
+ /**
90
129
  * Returns the status of delayed-withdrawal positions (redemption receipts)
91
130
  * owned by a liquidator wallet: what is receivable, how much, and when it
92
131
  * becomes claimable.
@@ -278,11 +317,14 @@ var LiquidationsService = class extends SDKConstruct {
278
317
  value: estimatedProfit,
279
318
  valueUsd: usd(estimatedProfit)
280
319
  },
281
- isDelayed: ca.tokens.some((t) => t.balance > 10n && !!this.sdk.withdrawalCompressor?.getWithdrawalSourceToken(t.token)),
320
+ isDelayed: ca.tokens.some((t) => t.balance > 10n && this.#isWithdrawalPhantom(t.token)),
282
321
  paused: suite.creditFacade.isPaused,
283
322
  rwa: market.rwa
284
323
  };
285
324
  }
325
+ #isWithdrawalPhantom(token) {
326
+ return !!this.sdk.withdrawalCompressor?.getWithdrawalSourceToken(token);
327
+ }
286
328
  #mainAsset(ca, market, fallback) {
287
329
  const asset = dominantCollateral(ca, market);
288
330
  if (!asset) return fallback;
@@ -1,5 +1,6 @@
1
1
  import { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS } from "./constants.js";
2
+ import { optimalPartialLiquidation, partialLiquidationParams } from "./partialLiquidation.js";
2
3
  import { LiquidationsService } from "./LiquidationsService.js";
3
4
  import { MultichainLiquidationsService } from "./MultichainLiquidationsService.js";
4
5
  import "./types.js";
5
- export { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService };
6
+ export { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService, optimalPartialLiquidation, partialLiquidationParams };
@@ -0,0 +1,72 @@
1
+ import { minSeizedAmount, optimalPartialLiquidationAmounts } from "../../market/math.js";
2
+ import { dominantCollateral } from "../../market/credit/collateralUtils.js";
3
+ import "../../market/index.js";
4
+ //#region src/onchain/accounts/liquidations/partialLiquidation.ts
5
+ /**
6
+ * Optimal partial liquidation of an account: the collateral to seize and the
7
+ * amounts that bring its health factor close to `optimalHF`.
8
+ *
9
+ * Ported from solidity, without the price updates the contract applies first:
10
+ * https://github.com/Gearbox-protocol/router-v3/blob/56e2d515ec6d9bb1e324e71c3708e59710779b24/contracts/liquidation/AbstractLiquidator.sol#L252
11
+ *
12
+ * @throws If no `tokenOut` can be picked.
13
+ **/
14
+ function optimalPartialLiquidation({ suite, account, optimalHF = suite.optimalHFForPartialLiquidation(account), exclude }) {
15
+ const tokenOut = bestTokenOut(suite, account, exclude);
16
+ return {
17
+ tokenOut,
18
+ ...optimalAmounts(suite, account, tokenOut, optimalHF)
19
+ };
20
+ }
21
+ /**
22
+ * Everything a partial liquidation of credit account needs, with any parameter
23
+ * the caller pinned down taken as given and the rest derived from current state.
24
+ *
25
+ * @throws If a derived `tokenOut` cannot be picked, or if the seized token is
26
+ * not a collateral token of the credit manager.
27
+ **/
28
+ function partialLiquidationParams({ suite, account, overrides = {}, exclude }) {
29
+ const tokenOut = overrides.tokenOut ?? bestTokenOut(suite, account, exclude);
30
+ const optimalHF = overrides.optimalHF ?? suite.optimalHFForPartialLiquidation(account);
31
+ const repaidAmount = overrides.repaidAmount ?? optimalAmounts(suite, account, tokenOut, optimalHF).repaidAmount;
32
+ return {
33
+ tokenOut,
34
+ optimalHF,
35
+ repaidAmount,
36
+ minSeizedAmount: overrides.minSeizedAmount ?? minSeizedAmountOf(suite, tokenOut, repaidAmount)
37
+ };
38
+ }
39
+ function bestTokenOut(suite, account, exclude) {
40
+ const collateral = dominantCollateral(account, suite.market, exclude);
41
+ if (!collateral) throw new Error(`cannot determine tokenOut for partial liquidation of ${suite.register.labelAddress(account.creditAccount)}: no enabled non-underlying collateral with value`);
42
+ return collateral;
43
+ }
44
+ /**
45
+ * @throws If `tokenOut` is not a collateral token of the credit manager.
46
+ **/
47
+ function optimalAmounts(suite, account, tokenOut, optimalHF) {
48
+ const { creditManager: cm, market } = suite;
49
+ const { feeLiquidation, liquidationDiscount } = suite.liquidationFees();
50
+ const ltTokenOut = cm.liquidationThresholds.get(tokenOut);
51
+ if (ltTokenOut === void 0) throw new Error(`token ${suite.register.labelAddress(tokenOut)} is not a collateral token in credit manager ${suite.register.labelAddress(cm.address)}`);
52
+ return optimalPartialLiquidationAmounts({
53
+ totalDebt: account.debt + account.accruedInterest + account.accruedFees,
54
+ twvUnderlying: market.priceOracle.convertFromUSD(market.underlying, account.twvUSD),
55
+ minDebt: suite.creditFacade.minDebt,
56
+ optimalHF,
57
+ liquidationDiscount: BigInt(liquidationDiscount),
58
+ discount: BigInt(liquidationDiscount) - BigInt(feeLiquidation),
59
+ ltTokenOut: BigInt(ltTokenOut)
60
+ });
61
+ }
62
+ /**
63
+ * Minimum amount of `token` that must be seized when repaying `repaidAmount`
64
+ * of underlying.
65
+ **/
66
+ function minSeizedAmountOf(suite, token, repaidAmount) {
67
+ const { market } = suite;
68
+ const tokenAmount = market.priceOracle.convert(market.underlying, token, repaidAmount);
69
+ return minSeizedAmount(tokenAmount, suite.liquidationFees().liquidationDiscount);
70
+ }
71
+ //#endregion
72
+ export { optimalPartialLiquidation, partialLiquidationParams };