@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.
- package/dist/cjs/abi/IWithdrawalCompressorV313.js +136 -0
- package/dist/cjs/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.js +24 -33
- package/dist/cjs/sdk/MultichainSDK.js +7 -0
- package/dist/cjs/sdk/OnchainSDK.js +14 -4
- package/dist/cjs/sdk/accounts/CreditAccountsServiceV310.js +16 -3
- package/dist/cjs/sdk/accounts/index.js +2 -0
- package/dist/cjs/sdk/accounts/liquidations/LiquidationsService.js +207 -0
- package/dist/cjs/sdk/accounts/liquidations/MultichainLiquidationsService.js +65 -0
- package/dist/cjs/sdk/accounts/liquidations/helpers.js +63 -0
- package/dist/cjs/sdk/accounts/liquidations/index.js +28 -0
- package/dist/cjs/sdk/accounts/liquidations/types.js +16 -0
- package/dist/cjs/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.js +142 -74
- package/dist/cjs/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +97 -0
- package/dist/cjs/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.js +1 -7
- package/dist/cjs/sdk/accounts/withdrawal-compressor/types.js +20 -0
- package/dist/esm/abi/IWithdrawalCompressorV313.js +136 -0
- package/dist/esm/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.js +24 -33
- package/dist/esm/sdk/MultichainSDK.js +7 -0
- package/dist/esm/sdk/OnchainSDK.js +16 -5
- package/dist/esm/sdk/accounts/CreditAccountsServiceV310.js +16 -3
- package/dist/esm/sdk/accounts/index.js +1 -0
- package/dist/esm/sdk/accounts/liquidations/LiquidationsService.js +188 -0
- package/dist/esm/sdk/accounts/liquidations/MultichainLiquidationsService.js +41 -0
- package/dist/esm/sdk/accounts/liquidations/helpers.js +36 -0
- package/dist/esm/sdk/accounts/liquidations/index.js +4 -0
- package/dist/esm/sdk/accounts/liquidations/types.js +0 -0
- package/dist/esm/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.js +138 -74
- package/dist/esm/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +104 -1
- package/dist/esm/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.js +1 -7
- package/dist/esm/sdk/accounts/withdrawal-compressor/types.js +12 -0
- package/dist/types/abi/IWithdrawalCompressorV313.d.ts +200 -0
- package/dist/types/plugins/delayed-withdrawal/DelayedWithdrawalPlugin.d.ts +7 -7
- package/dist/types/sdk/MultichainSDK.d.ts +6 -0
- package/dist/types/sdk/OnchainSDK.d.ts +8 -5
- package/dist/types/sdk/accounts/CreditAccountsServiceV310.d.ts +1 -1
- package/dist/types/sdk/accounts/index.d.ts +1 -0
- package/dist/types/sdk/accounts/liquidations/LiquidationsService.d.ts +20 -0
- package/dist/types/sdk/accounts/liquidations/MultichainLiquidationsService.d.ts +23 -0
- package/dist/types/sdk/accounts/liquidations/helpers.d.ts +34 -0
- package/dist/types/sdk/accounts/liquidations/index.d.ts +4 -0
- package/dist/types/sdk/accounts/liquidations/types.d.ts +172 -0
- package/dist/types/sdk/accounts/types.d.ts +6 -0
- package/dist/types/sdk/accounts/withdrawal-compressor/AbstractWithdrawalCompressorContract.d.ts +80 -6
- package/dist/types/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.d.ts +222 -0
- package/dist/types/sdk/accounts/withdrawal-compressor/createWithdrawalCompressor.d.ts +5 -4
- package/dist/types/sdk/accounts/withdrawal-compressor/types.d.ts +106 -7
- package/dist/types/sdk/types/state.d.ts +8 -0
- 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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
24
|
-
}
|
|
25
|
-
for (const [creditManager, assets] of byCreditManager) {
|
|
26
|
-
this.#withdrawableAssets.upsert(creditManager, assets);
|
|
38
|
+
result.upsert(cfg.creditManager, assets);
|
|
27
39
|
}
|
|
28
|
-
return
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
+
};
|
|
File without changes
|