@gearbox-protocol/sdk 14.12.0-next.34 → 14.12.0-next.35

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 (48) hide show
  1. package/dist/cjs/abi/IWithdrawalCompressorV313.js +136 -0
  2. package/dist/cjs/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.js +24 -33
  3. package/dist/cjs/sdk/MultichainSDK.js +7 -0
  4. package/dist/cjs/sdk/OnchainSDK.js +14 -4
  5. package/dist/cjs/sdk/accounts/CreditAccountsServiceV310.js +16 -3
  6. package/dist/cjs/sdk/accounts/index.js +2 -0
  7. package/dist/cjs/sdk/accounts/liquidations/LiquidationsService.js +207 -0
  8. package/dist/cjs/sdk/accounts/liquidations/MultichainLiquidationsService.js +65 -0
  9. package/dist/cjs/sdk/accounts/liquidations/helpers.js +63 -0
  10. package/dist/cjs/sdk/accounts/liquidations/index.js +28 -0
  11. package/dist/cjs/sdk/accounts/liquidations/types.js +16 -0
  12. package/dist/cjs/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.js +142 -74
  13. package/dist/cjs/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +97 -0
  14. package/dist/cjs/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.js +1 -7
  15. package/dist/cjs/sdk/accounts/withdrawal-compressor/types.js +20 -0
  16. package/dist/esm/abi/IWithdrawalCompressorV313.js +136 -0
  17. package/dist/esm/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.js +24 -33
  18. package/dist/esm/sdk/MultichainSDK.js +7 -0
  19. package/dist/esm/sdk/OnchainSDK.js +16 -5
  20. package/dist/esm/sdk/accounts/CreditAccountsServiceV310.js +16 -3
  21. package/dist/esm/sdk/accounts/index.js +1 -0
  22. package/dist/esm/sdk/accounts/liquidations/LiquidationsService.js +188 -0
  23. package/dist/esm/sdk/accounts/liquidations/MultichainLiquidationsService.js +41 -0
  24. package/dist/esm/sdk/accounts/liquidations/helpers.js +36 -0
  25. package/dist/esm/sdk/accounts/liquidations/index.js +4 -0
  26. package/dist/esm/sdk/accounts/liquidations/types.js +0 -0
  27. package/dist/esm/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.js +138 -74
  28. package/dist/esm/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +104 -1
  29. package/dist/esm/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.js +1 -7
  30. package/dist/esm/sdk/accounts/withdrawal-compressor/types.js +12 -0
  31. package/dist/types/abi/IWithdrawalCompressorV313.d.ts +200 -0
  32. package/dist/types/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.d.ts +7 -7
  33. package/dist/types/sdk/MultichainSDK.d.ts +6 -0
  34. package/dist/types/sdk/OnchainSDK.d.ts +8 -5
  35. package/dist/types/sdk/accounts/CreditAccountsServiceV310.d.ts +1 -1
  36. package/dist/types/sdk/accounts/index.d.ts +1 -0
  37. package/dist/types/sdk/accounts/liquidations/LiquidationsService.d.ts +20 -0
  38. package/dist/types/sdk/accounts/liquidations/MultichainLiquidationsService.d.ts +23 -0
  39. package/dist/types/sdk/accounts/liquidations/helpers.d.ts +34 -0
  40. package/dist/types/sdk/accounts/liquidations/index.d.ts +4 -0
  41. package/dist/types/sdk/accounts/liquidations/types.d.ts +172 -0
  42. package/dist/types/sdk/accounts/types.d.ts +6 -0
  43. package/dist/types/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.d.ts +80 -6
  44. package/dist/types/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.d.ts +222 -0
  45. package/dist/types/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.d.ts +5 -4
  46. package/dist/types/sdk/accounts/withdrawal-compressor/types.d.ts +106 -7
  47. package/dist/types/sdk/types/state.d.ts +8 -0
  48. package/package.json +1 -1
@@ -79,6 +79,100 @@ const iWithdrawalCompressorV313Abi = [
79
79
  ],
80
80
  stateMutability: "view"
81
81
  },
82
+ {
83
+ type: "function",
84
+ name: "getExternalAccountCurrentWithdrawals",
85
+ inputs: [
86
+ { name: "withdrawalToken", type: "address", internalType: "address" },
87
+ { name: "account", type: "address", internalType: "address" }
88
+ ],
89
+ outputs: [
90
+ {
91
+ name: "",
92
+ type: "tuple[]",
93
+ internalType: "struct ClaimableWithdrawal[]",
94
+ components: [
95
+ { name: "token", type: "address", internalType: "address" },
96
+ {
97
+ name: "withdrawalPhantomToken",
98
+ type: "address",
99
+ internalType: "address"
100
+ },
101
+ {
102
+ name: "withdrawalTokenSpent",
103
+ type: "uint256",
104
+ internalType: "uint256"
105
+ },
106
+ {
107
+ name: "outputs",
108
+ type: "tuple[]",
109
+ internalType: "struct WithdrawalOutput[]",
110
+ components: [
111
+ { name: "token", type: "address", internalType: "address" },
112
+ { name: "isDelayed", type: "bool", internalType: "bool" },
113
+ { name: "amount", type: "uint256", internalType: "uint256" }
114
+ ]
115
+ },
116
+ {
117
+ name: "claimCalls",
118
+ type: "tuple[]",
119
+ internalType: "struct MultiCall[]",
120
+ components: [
121
+ { name: "target", type: "address", internalType: "address" },
122
+ { name: "callData", type: "bytes", internalType: "bytes" }
123
+ ]
124
+ },
125
+ { name: "extraData", type: "bytes", internalType: "bytes" }
126
+ ]
127
+ },
128
+ {
129
+ name: "",
130
+ type: "tuple[]",
131
+ internalType: "struct PendingWithdrawal[]",
132
+ components: [
133
+ { name: "token", type: "address", internalType: "address" },
134
+ {
135
+ name: "withdrawalPhantomToken",
136
+ type: "address",
137
+ internalType: "address"
138
+ },
139
+ {
140
+ name: "expectedOutputs",
141
+ type: "tuple[]",
142
+ internalType: "struct WithdrawalOutput[]",
143
+ components: [
144
+ { name: "token", type: "address", internalType: "address" },
145
+ { name: "isDelayed", type: "bool", internalType: "bool" },
146
+ { name: "amount", type: "uint256", internalType: "uint256" }
147
+ ]
148
+ },
149
+ { name: "claimableAt", type: "uint256", internalType: "uint256" },
150
+ { name: "extraData", type: "bytes", internalType: "bytes" }
151
+ ]
152
+ }
153
+ ],
154
+ stateMutability: "view"
155
+ },
156
+ {
157
+ type: "function",
158
+ name: "getWithdrawalStatus",
159
+ inputs: [{ name: "redeemer", type: "address", internalType: "address" }],
160
+ outputs: [
161
+ { name: "", type: "uint8", internalType: "enum WithdrawalStatus" }
162
+ ],
163
+ stateMutability: "view"
164
+ },
165
+ {
166
+ type: "function",
167
+ name: "getWithdrawalStatus",
168
+ inputs: [
169
+ { name: "redeemers", type: "address[]", internalType: "address[]" }
170
+ ],
171
+ outputs: [
172
+ { name: "", type: "uint8[]", internalType: "enum WithdrawalStatus[]" }
173
+ ],
174
+ stateMutability: "view"
175
+ },
82
176
  {
83
177
  type: "function",
84
178
  name: "getWithdrawableAssets",
@@ -152,6 +246,48 @@ const iWithdrawalCompressorV313Abi = [
152
246
  ],
153
247
  stateMutability: "view"
154
248
  },
249
+ {
250
+ type: "function",
251
+ name: "getWithdrawalRequestResult",
252
+ inputs: [
253
+ { name: "creditAccount", type: "address", internalType: "address" },
254
+ { name: "token", type: "address", internalType: "address" },
255
+ { name: "withdrawalToken", type: "address", internalType: "address" },
256
+ { name: "amount", type: "uint256", internalType: "uint256" }
257
+ ],
258
+ outputs: [
259
+ {
260
+ name: "",
261
+ type: "tuple",
262
+ internalType: "struct RequestableWithdrawal",
263
+ components: [
264
+ { name: "token", type: "address", internalType: "address" },
265
+ { name: "amountIn", type: "uint256", internalType: "uint256" },
266
+ {
267
+ name: "outputs",
268
+ type: "tuple[]",
269
+ internalType: "struct WithdrawalOutput[]",
270
+ components: [
271
+ { name: "token", type: "address", internalType: "address" },
272
+ { name: "isDelayed", type: "bool", internalType: "bool" },
273
+ { name: "amount", type: "uint256", internalType: "uint256" }
274
+ ]
275
+ },
276
+ {
277
+ name: "requestCalls",
278
+ type: "tuple[]",
279
+ internalType: "struct MultiCall[]",
280
+ components: [
281
+ { name: "target", type: "address", internalType: "address" },
282
+ { name: "callData", type: "bytes", internalType: "bytes" }
283
+ ]
284
+ },
285
+ { name: "claimableAt", type: "uint256", internalType: "uint256" }
286
+ ]
287
+ }
288
+ ],
289
+ stateMutability: "view"
290
+ },
155
291
  {
156
292
  type: "function",
157
293
  name: "getWithdrawalRequestResult",
@@ -1,16 +1,31 @@
1
1
  import { AddressMap, BasePlugin } from "../../sdk/index.js";
2
2
  const MAP_LABEL = "delayedWithdrawal";
3
3
  class DelayedWithdrawalPlugin extends BasePlugin {
4
- #withdrawableAssets;
5
4
  async load(force) {
6
5
  if (!force && this.loaded) {
7
6
  return this.state;
8
7
  }
9
- this.#withdrawableAssets = new AddressMap(void 0, MAP_LABEL);
10
- const resp = await this.sdk.withdrawalCompressor.getWithdrawableAssetsBatch();
11
- const byCreditManager = /* @__PURE__ */ new Map();
12
- for (const cfg of resp) {
13
- const assets = byCreditManager.get(cfg.creditManager) ?? [];
8
+ await this.sdk.withdrawalCompressor?.loadWithdrawableAssets(force);
9
+ return this.state;
10
+ }
11
+ get loaded() {
12
+ return !!this.sdk.withdrawalCompressor?.state;
13
+ }
14
+ /**
15
+ * Returns a map of cmAddress -> array of delayed assets
16
+ * @throws if withdrawable assets have not been loaded
17
+ */
18
+ get withdrawableAssets() {
19
+ const compressor = this.sdk.withdrawalCompressor;
20
+ if (!compressor?.state) {
21
+ throw new Error("withdrawable assets are not loaded");
22
+ }
23
+ const result = new AddressMap(
24
+ void 0,
25
+ MAP_LABEL
26
+ );
27
+ for (const cfg of compressor.getWithdrawableAssets()) {
28
+ const assets = result.get(cfg.creditManager) ?? [];
14
29
  assets.push({
15
30
  ...cfg,
16
31
  disabled: isConfigToOmit({
@@ -20,25 +35,9 @@ class DelayedWithdrawalPlugin extends BasePlugin {
20
35
  underlying: cfg.underlying
21
36
  })
22
37
  });
23
- byCreditManager.set(cfg.creditManager, assets);
24
- }
25
- for (const [creditManager, assets] of byCreditManager) {
26
- this.#withdrawableAssets.upsert(creditManager, assets);
38
+ result.upsert(cfg.creditManager, assets);
27
39
  }
28
- return this.state;
29
- }
30
- get loaded() {
31
- return !!this.#withdrawableAssets;
32
- }
33
- /**
34
- * Returns a map of cmAddress -> array of delayed assets
35
- * @throws if plugin is not attached
36
- */
37
- get withdrawableAssets() {
38
- if (!this.#withdrawableAssets) {
39
- throw new Error("withdrawable assets plugin not attached");
40
- }
41
- return this.#withdrawableAssets;
40
+ return result;
42
41
  }
43
42
  stateHuman(_) {
44
43
  return this.withdrawableAssets.values().flatMap((cm) => {
@@ -53,15 +52,7 @@ class DelayedWithdrawalPlugin extends BasePlugin {
53
52
  });
54
53
  }
55
54
  get state() {
56
- return {
57
- withdrawableAssets: this.withdrawableAssets.asRecord()
58
- };
59
- }
60
- hydrate(state) {
61
- this.#withdrawableAssets = new AddressMap(
62
- Object.entries(state.withdrawableAssets),
63
- MAP_LABEL
64
- );
55
+ return {};
65
56
  }
66
57
  }
67
58
  function getConfigKey(config) {
@@ -1,3 +1,4 @@
1
+ import { MultichainLiquidationsService } from "./accounts/index.js";
1
2
  import { getNetworkType } from "./chain/chains.js";
2
3
  import {
3
4
  SdkMissingChainStateError,
@@ -16,6 +17,11 @@ class MultichainSDK {
16
17
  #redstoneCache;
17
18
  #pythCache;
18
19
  #logger;
20
+ /**
21
+ * Namespace for liquidatable credit accounts discovery across all
22
+ * configured chains.
23
+ */
24
+ liquidations;
19
25
  constructor(options) {
20
26
  this.#chains = /* @__PURE__ */ new Map();
21
27
  this.#logger = options.logger;
@@ -43,6 +49,7 @@ class MultichainSDK {
43
49
  );
44
50
  this.#chains.set(network, sdk);
45
51
  }
52
+ this.liquidations = new MultichainLiquidationsService(this);
46
53
  }
47
54
  /**
48
55
  * Attach all configured chains in parallel.
@@ -6,7 +6,8 @@ import {
6
6
  } from "viem";
7
7
  import {
8
8
  CreditAccountsServiceV310,
9
- createWithdrawalCompressor
9
+ createWithdrawalCompressor,
10
+ LiquidationsService
10
11
  } from "./accounts/index.js";
11
12
  import { ChainContractsRegister } from "./base/index.js";
12
13
  import { getChain } from "./chain/index.js";
@@ -67,6 +68,7 @@ class OnchainSDK extends ChainContractsRegister {
67
68
  #rwa;
68
69
  #marketRegister;
69
70
  #priceFeeds;
71
+ #withdrawalCompressor;
70
72
  /**
71
73
  * Gas limit applied to read-only `eth_call` requests.
72
74
  **/
@@ -83,6 +85,10 @@ class OnchainSDK extends ChainContractsRegister {
83
85
  * Namespace for pool operations.
84
86
  */
85
87
  pools;
88
+ /**
89
+ * Namespace for liquidatable credit accounts discovery.
90
+ */
91
+ liquidations;
86
92
  /**
87
93
  * @param network - Gearbox network type (e.g. `"Mainnet"`, `"Monad"`).
88
94
  * @param clientOptions - Connection options (RPC URLs, transport, or client).
@@ -109,6 +115,8 @@ class OnchainSDK extends ChainContractsRegister {
109
115
  }
110
116
  this.accounts = new CreditAccountsServiceV310(this);
111
117
  this.pools = new PoolService(this);
118
+ this.liquidations = new LiquidationsService(this);
119
+ this.#withdrawalCompressor = createWithdrawalCompressor(this);
112
120
  }
113
121
  /**
114
122
  * Initialises the SDK by reading live on-chain state.
@@ -255,6 +263,9 @@ class OnchainSDK extends ChainContractsRegister {
255
263
  this.#marketRegister.hydrate(state);
256
264
  this.#rwa = new RWARegistry(this);
257
265
  this.#rwa.setState(state.rwa);
266
+ if (state.withdrawals) {
267
+ this.#withdrawalCompressor?.hydrate(state.withdrawals);
268
+ }
258
269
  for (const [name, plugin] of TypedObjectUtils.entries(this.plugins)) {
259
270
  const pluginState = state.plugins[name];
260
271
  if (plugin.hydrate && pluginState) {
@@ -319,6 +330,7 @@ class OnchainSDK extends ChainContractsRegister {
319
330
  addressProvider: this.addressProvider.state,
320
331
  ...this.marketRegister.state,
321
332
  rwa: this.#rwa.state,
333
+ withdrawals: this.#withdrawalCompressor?.state,
322
334
  plugins: Object.fromEntries(
323
335
  TypedObjectUtils.entries(this.plugins).map(([name, plugin]) => [
324
336
  name,
@@ -536,12 +548,11 @@ class OnchainSDK extends ChainContractsRegister {
536
548
  return createRouter(this, routerAddr, routerV);
537
549
  }
538
550
  /**
539
- * Lazily returns a withdrawal compressor contract for the current chain.
540
- *
541
- * @throws If no withdrawal compressor is supported on the current chain.
551
+ * Withdrawal compressor contract for the current chain, or `undefined`
552
+ * when no withdrawal compressor is supported on it.
542
553
  **/
543
554
  get withdrawalCompressor() {
544
- return createWithdrawalCompressor(this);
555
+ return this.#withdrawalCompressor;
545
556
  }
546
557
  }
547
558
  export {
@@ -767,14 +767,16 @@ class CreditAccountsServiceV310 extends SDKConstruct {
767
767
  creditAccount,
768
768
  amount,
769
769
  token,
770
+ withdrawalPhantomToken,
770
771
  intent
771
772
  }) {
772
- return this.sdk.withdrawalCompressor.getWithdrawalRequestResult(
773
+ return this.#withdrawalCompressor.getWithdrawalRequestResult({
773
774
  creditAccount,
774
775
  token,
775
776
  amount,
777
+ withdrawalPhantomToken,
776
778
  intent
777
- );
779
+ });
778
780
  }
779
781
  /**
780
782
  * {@inheritDoc ICreditAccountsService.getPendingWithdrawals}
@@ -782,7 +784,7 @@ class CreditAccountsServiceV310 extends SDKConstruct {
782
784
  async getPendingWithdrawals({
783
785
  creditAccount
784
786
  }) {
785
- const { claimable, pending } = await this.sdk.withdrawalCompressor.getCurrentWithdrawals(creditAccount);
787
+ const { claimable, pending } = await this.#withdrawalCompressor.getCurrentWithdrawals(creditAccount);
786
788
  return {
787
789
  claimableNow: claimable,
788
790
  pending
@@ -1849,6 +1851,17 @@ class CreditAccountsServiceV310 extends SDKConstruct {
1849
1851
  }
1850
1852
  return suite.creditFacade.multicall(creditAccount, calls);
1851
1853
  }
1854
+ /**
1855
+ * Withdrawal compressor of the current chain.
1856
+ * @throws If no withdrawal compressor is supported on the current chain.
1857
+ **/
1858
+ get #withdrawalCompressor() {
1859
+ const compressor = this.sdk.withdrawalCompressor;
1860
+ if (!compressor) {
1861
+ throw new Error(`no withdrawal compressor on ${this.sdk.networkType}`);
1862
+ }
1863
+ return compressor;
1864
+ }
1852
1865
  }
1853
1866
  export {
1854
1867
  CreditAccountsServiceV310
@@ -1,3 +1,4 @@
1
1
  export * from "./CreditAccountsServiceV310.js";
2
+ export * from "./liquidations/index.js";
2
3
  export * from "./types.js";
3
4
  export * from "./withdrawal-compressor/index.js";
@@ -0,0 +1,188 @@
1
+ import { SDKConstruct } from "../../base/index.js";
2
+ import { WAD } from "../../constants/index.js";
3
+ import { AddressSet } from "../../utils/index.js";
4
+ import {
5
+ calcEstimatedProfit,
6
+ calcRepaymentAmount,
7
+ DUST_THRESHOLD,
8
+ pickMainAsset
9
+ } from "./helpers.js";
10
+ class LiquidationsService extends SDKConstruct {
11
+ /**
12
+ * {@inheritDoc ILiquidationsService.getLiquidatableAccounts}
13
+ **/
14
+ async getLiquidatableAccounts(props) {
15
+ if (props?.networks && !props.networks.includes(this.sdk.networkType)) {
16
+ return [];
17
+ }
18
+ await this.sdk.withdrawalCompressor?.loadWithdrawableAssets();
19
+ const accounts = await this.sdk.accounts.getCreditAccounts({
20
+ maxHealthFactor: WAD - 1n,
21
+ includeZeroDebt: false
22
+ });
23
+ const seen = new AddressSet(accounts.map((ca) => ca.creditAccount));
24
+ for (const ca of await this.#getExpiredCreditAccounts()) {
25
+ if (!seen.has(ca.creditAccount)) {
26
+ seen.add(ca.creditAccount);
27
+ accounts.push(ca);
28
+ }
29
+ }
30
+ const rows = [];
31
+ for (const ca of accounts) {
32
+ if (!ca.success) {
33
+ continue;
34
+ }
35
+ rows.push(this.#buildAccount(ca));
36
+ }
37
+ const allowedAssets = props?.assets?.length ? new AddressSet(props.assets) : void 0;
38
+ return rows.filter((row) => {
39
+ if (props?.delayed !== void 0 && row.isDelayed !== props.delayed) {
40
+ return false;
41
+ }
42
+ if (allowedAssets && !allowedAssets.has(row.asset)) {
43
+ return false;
44
+ }
45
+ return true;
46
+ });
47
+ }
48
+ /**
49
+ * {@inheritDoc ILiquidationsService.getLiquidationDetails}
50
+ **/
51
+ async getLiquidationDetails(props) {
52
+ const { network, creditAccount } = props;
53
+ if (network !== this.sdk.networkType) {
54
+ throw new Error(
55
+ `network mismatch: this SDK is attached to ${this.sdk.networkType}, requested ${network}`
56
+ );
57
+ }
58
+ const ca = await this.sdk.accounts.getCreditAccountData(creditAccount);
59
+ if (!ca) {
60
+ throw new Error(`credit account ${creditAccount} not found`);
61
+ }
62
+ if (!ca.success) {
63
+ throw new Error(
64
+ `cannot compute liquidation details for ${creditAccount}: collateral computation failed`
65
+ );
66
+ }
67
+ const compressor = this.sdk.withdrawalCompressor;
68
+ await compressor?.loadWithdrawableAssets();
69
+ const account = this.#buildAccount(ca);
70
+ const receivedAssets = [];
71
+ for (const t of ca.tokens) {
72
+ if (t.balance > DUST_THRESHOLD && !compressor?.getWithdrawalSourceToken(t.token)) {
73
+ receivedAssets.push({
74
+ isDelayed: false,
75
+ token: t.token,
76
+ amount: t.balance
77
+ });
78
+ }
79
+ }
80
+ if (account.isDelayed && compressor) {
81
+ const { claimable, pending } = await compressor.getCurrentWithdrawals(creditAccount);
82
+ for (const w of claimable) {
83
+ for (const o of w.outputs) {
84
+ receivedAssets.push({
85
+ isDelayed: true,
86
+ token: o.token,
87
+ amount: o.amount,
88
+ sourceToken: w.token
89
+ });
90
+ }
91
+ }
92
+ for (const w of pending) {
93
+ for (const o of w.expectedOutputs) {
94
+ receivedAssets.push({
95
+ isDelayed: true,
96
+ token: o.token,
97
+ amount: o.amount,
98
+ sourceToken: w.token,
99
+ claimableAt: w.claimableAt
100
+ });
101
+ }
102
+ }
103
+ }
104
+ return { ...account, receivedAssets };
105
+ }
106
+ /**
107
+ * Accounts of expired credit managers with outstanding debt are liquidatable
108
+ * regardless of their health factor.
109
+ **/
110
+ async #getExpiredCreditAccounts() {
111
+ const expiredCMs = [];
112
+ for (const market of this.sdk.marketRegister.markets) {
113
+ if (market.pool.pool.totalBorrowed === 0n) {
114
+ continue;
115
+ }
116
+ for (const cm of market.creditManagers) {
117
+ const borrowed = market.pool.pool.creditManagerDebtParams.get(cm.creditManager.address)?.borrowed ?? 0n;
118
+ if (cm.isExpired && borrowed > 0n) {
119
+ expiredCMs.push(cm.creditManager.address);
120
+ }
121
+ }
122
+ }
123
+ if (expiredCMs.length === 0) {
124
+ return [];
125
+ }
126
+ this.logger?.debug(
127
+ `getting credit accounts of ${expiredCMs.length} expired credit managers`
128
+ );
129
+ const result = [];
130
+ for (const creditManager of expiredCMs) {
131
+ const accounts = await this.sdk.accounts.getCreditAccounts({
132
+ creditManager,
133
+ includeZeroDebt: false
134
+ });
135
+ result.push(...accounts);
136
+ }
137
+ return result;
138
+ }
139
+ /**
140
+ * Requires the compressor's withdrawable assets cache to be loaded
141
+ * (see `loadWithdrawableAssets`) so that phantom token lookups are sync.
142
+ **/
143
+ #buildAccount(ca) {
144
+ const compressor = this.sdk.withdrawalCompressor;
145
+ const suite = this.sdk.marketRegister.findCreditManager(ca.creditManager);
146
+ const market = suite.market;
147
+ const meta = this.sdk.tokensMeta.get(suite.underlying);
148
+ const unwrappedUnderlying = meta && this.sdk.tokensMeta.isRWAUnderlying(meta) ? meta.asset : suite.underlying;
149
+ const liquidationDiscount = suite.isExpired ? suite.creditManager.liquidationDiscountExpired : suite.creditManager.liquidationDiscount;
150
+ let asset = pickMainAsset(ca, (token, balance) => {
151
+ try {
152
+ return market.priceOracle.convert(token, market.underlying, balance);
153
+ } catch {
154
+ return 0n;
155
+ }
156
+ });
157
+ if (asset) {
158
+ asset = compressor?.getWithdrawalSourceToken(asset) ?? asset;
159
+ } else {
160
+ asset = unwrappedUnderlying;
161
+ }
162
+ return {
163
+ creditAccount: ca.creditAccount,
164
+ creditManager: ca.creditManager,
165
+ network: this.sdk.networkType,
166
+ asset,
167
+ totalValue: {
168
+ token: unwrappedUnderlying,
169
+ balance: ca.totalValue
170
+ },
171
+ totalValueUSD: ca.totalValueUSD,
172
+ repaymentAmount: {
173
+ token: unwrappedUnderlying,
174
+ balance: calcRepaymentAmount(ca.totalValue, liquidationDiscount)
175
+ },
176
+ estimatedProfit: {
177
+ token: unwrappedUnderlying,
178
+ balance: calcEstimatedProfit(ca.totalValue, liquidationDiscount)
179
+ },
180
+ isDelayed: ca.tokens.some(
181
+ (t) => t.balance > DUST_THRESHOLD && !!compressor?.getWithdrawalSourceToken(t.token)
182
+ )
183
+ };
184
+ }
185
+ }
186
+ export {
187
+ LiquidationsService
188
+ };
@@ -0,0 +1,41 @@
1
+ class MultichainLiquidationsService {
2
+ #sdk;
3
+ constructor(sdk) {
4
+ this.#sdk = sdk;
5
+ }
6
+ /**
7
+ * {@inheritDoc ILiquidationsService.getLiquidatableAccounts}
8
+ **/
9
+ async getLiquidatableAccounts(props) {
10
+ const chains = [...this.#sdk.chains.entries()].filter(
11
+ ([network]) => !props?.networks || props.networks.includes(network)
12
+ );
13
+ const results = await Promise.allSettled(
14
+ chains.map(
15
+ ([, chainSdk]) => chainSdk.liquidations.getLiquidatableAccounts(props)
16
+ )
17
+ );
18
+ const accounts = [];
19
+ results.forEach((result, i) => {
20
+ const [network, chainSdk] = chains[i];
21
+ if (result.status === "fulfilled") {
22
+ accounts.push(...result.value);
23
+ } else {
24
+ chainSdk.logger?.warn(
25
+ result.reason,
26
+ `failed to get liquidatable accounts on ${network}`
27
+ );
28
+ }
29
+ });
30
+ return accounts;
31
+ }
32
+ /**
33
+ * {@inheritDoc ILiquidationsService.getLiquidationDetails}
34
+ **/
35
+ async getLiquidationDetails(props) {
36
+ return this.#sdk.chain(props.network).liquidations.getLiquidationDetails(props);
37
+ }
38
+ }
39
+ export {
40
+ MultichainLiquidationsService
41
+ };
@@ -0,0 +1,36 @@
1
+ import { PERCENTAGE_FACTOR } from "../../constants/index.js";
2
+ import { hexEq } from "../../utils/index.js";
3
+ const DUST_THRESHOLD = 10n;
4
+ function calcRepaymentAmount(totalValue, liquidationDiscount) {
5
+ return totalValue * BigInt(liquidationDiscount) / PERCENTAGE_FACTOR;
6
+ }
7
+ function calcEstimatedProfit(totalValue, liquidationDiscount) {
8
+ return totalValue * (PERCENTAGE_FACTOR - BigInt(liquidationDiscount)) / PERCENTAGE_FACTOR;
9
+ }
10
+ function pickMainAsset(ca, convert) {
11
+ let bestValue = 0n;
12
+ let bestToken;
13
+ for (const t of ca.tokens) {
14
+ if (hexEq(t.token, ca.underlying)) {
15
+ continue;
16
+ }
17
+ if ((t.mask & ca.enabledTokensMask) === 0n) {
18
+ continue;
19
+ }
20
+ if (t.balance <= DUST_THRESHOLD) {
21
+ continue;
22
+ }
23
+ const value = convert(t.token, t.balance);
24
+ if (value > bestValue) {
25
+ bestValue = value;
26
+ bestToken = t.token;
27
+ }
28
+ }
29
+ return bestToken;
30
+ }
31
+ export {
32
+ DUST_THRESHOLD,
33
+ calcEstimatedProfit,
34
+ calcRepaymentAmount,
35
+ pickMainAsset
36
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./helpers.js";
2
+ export * from "./LiquidationsService.js";
3
+ export * from "./MultichainLiquidationsService.js";
4
+ export * from "./types.js";
File without changes