@gearbox-protocol/sdk 14.1.1 → 14.2.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.
- package/dist/cjs/dev/claimDSToken.js +208 -0
- package/dist/cjs/plugins/accounts/AccountsPlugin.js +1 -2
- package/dist/cjs/sdk/OnchainSDK.js +12 -0
- package/dist/cjs/sdk/accounts/CreditAccountsServiceV310.js +1448 -23
- package/dist/cjs/sdk/accounts/index.js +0 -4
- package/dist/cjs/sdk/market/credit/CreditFacadeV310Contract.js +6 -0
- package/dist/cjs/sdk/market/kyc/securitize/SecuritizeKYCFactory.js +19 -2
- package/dist/cjs/sdk/market/kyc/securitize/types.js +20 -0
- package/dist/cjs/sdk/market/oracle/PriceOracleV310Contract.js +26 -0
- package/dist/cjs/sdk/market/pricefeeds/PriceFeedsRegister.js +3 -0
- package/dist/esm/dev/claimDSToken.js +187 -0
- package/dist/esm/plugins/accounts/AccountsPlugin.js +2 -7
- package/dist/esm/sdk/OnchainSDK.js +14 -0
- package/dist/esm/sdk/accounts/CreditAccountsServiceV310.js +1466 -23
- package/dist/esm/sdk/accounts/index.js +0 -2
- package/dist/esm/sdk/accounts/multicall-utils.js +1 -5
- package/dist/esm/sdk/market/credit/CreditFacadeV310Contract.js +6 -0
- package/dist/esm/sdk/market/kyc/securitize/SecuritizeKYCFactory.js +21 -2
- package/dist/esm/sdk/market/kyc/securitize/types.js +12 -0
- package/dist/esm/sdk/market/oracle/PriceOracleV310Contract.js +26 -0
- package/dist/esm/sdk/market/pricefeeds/PriceFeedsRegister.js +3 -0
- package/dist/types/dev/claimDSToken.d.ts +34 -0
- package/dist/types/sdk/OnchainSDK.d.ts +10 -0
- package/dist/types/sdk/accounts/CreditAccountsServiceV310.d.ts +181 -3
- package/dist/types/sdk/accounts/index.d.ts +0 -2
- package/dist/types/sdk/accounts/types.d.ts +56 -0
- package/dist/types/sdk/market/credit/CreditFacadeV310Contract.d.ts +2 -0
- package/dist/types/sdk/market/kyc/securitize/SecuritizeKYCFactory.d.ts +1 -1
- package/dist/types/sdk/market/kyc/securitize/types.d.ts +37 -22
- package/dist/types/sdk/market/oracle/PriceOracleBaseContract.d.ts +5 -0
- package/dist/types/sdk/market/oracle/PriceOracleV310Contract.d.ts +4 -0
- package/dist/types/sdk/market/oracle/types.d.ts +9 -0
- package/dist/types/sdk/pools/types.d.ts +19 -0
- package/package.json +1 -1
- package/dist/cjs/sdk/accounts/AbstractCreditAccountsService.js +0 -1411
- package/dist/cjs/sdk/accounts/createCreditAccountService.js +0 -35
- package/dist/esm/sdk/accounts/AbstractCreditAccountsService.js +0 -1405
- package/dist/esm/sdk/accounts/createCreditAccountService.js +0 -11
- package/dist/types/sdk/accounts/AbstractCreditAccountsService.d.ts +0 -226
- package/dist/types/sdk/accounts/createCreditAccountService.d.ts +0 -9
|
@@ -1,8 +1,1119 @@
|
|
|
1
|
+
import { ierc4626AdapterAbi } from "@gearbox-protocol/integrations-v3";
|
|
1
2
|
import { encodeFunctionData, getContract } from "viem";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
iBotListV310Abi,
|
|
5
|
+
iCreditFacadeMulticallV310Abi
|
|
6
|
+
} from "../../abi/310/generated.js";
|
|
7
|
+
import { creditAccountCompressorAbi } from "../../abi/compressors/creditAccountCompressor.js";
|
|
8
|
+
import { peripheryCompressorAbi } from "../../abi/compressors/peripheryCompressor.js";
|
|
9
|
+
import { rewardsCompressorAbi } from "../../abi/compressors/rewardsCompressor.js";
|
|
10
|
+
import { iWithdrawalCompressorV310Abi } from "../../abi/IWithdrawalCompressorV310.js";
|
|
11
|
+
import { iBaseRewardPoolAbi } from "../../abi/iBaseRewardPool.js";
|
|
12
|
+
import { iKYCFactoryAbi } from "../../abi/kyc/iKYCFactory.js";
|
|
13
|
+
import { SDKConstruct } from "../base/index.js";
|
|
14
|
+
import { chains } from "../chain/chains.js";
|
|
15
|
+
import {
|
|
16
|
+
ADDRESS_0X0,
|
|
17
|
+
AP_CREDIT_ACCOUNT_COMPRESSOR,
|
|
18
|
+
AP_PERIPHERY_COMPRESSOR,
|
|
19
|
+
AP_REWARDS_COMPRESSOR,
|
|
20
|
+
MAX_UINT256,
|
|
21
|
+
MIN_INT96,
|
|
22
|
+
PERCENTAGE_FACTOR,
|
|
23
|
+
RAY,
|
|
24
|
+
VERSION_RANGE_310
|
|
25
|
+
} from "../constants/index.js";
|
|
26
|
+
import {
|
|
27
|
+
getRawPriceUpdates
|
|
28
|
+
} from "../market/index.js";
|
|
29
|
+
import { assetsMap } from "../router/index.js";
|
|
30
|
+
import { AddressMap, AddressSet, hexEq } from "../utils/index.js";
|
|
31
|
+
import { simulateWithPriceUpdates } from "../utils/viem/index.js";
|
|
32
|
+
import {
|
|
33
|
+
extractPriceUpdates,
|
|
34
|
+
extractQuotaTokens,
|
|
35
|
+
mergePriceUpdates
|
|
36
|
+
} from "./multicall-utils.js";
|
|
37
|
+
const COMPRESSORS = {
|
|
38
|
+
[chains.Mainnet.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023",
|
|
39
|
+
[chains.Monad.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023"
|
|
40
|
+
};
|
|
41
|
+
function getWithdrawalCompressorAddress(chainId) {
|
|
42
|
+
return COMPRESSORS[chainId];
|
|
43
|
+
}
|
|
44
|
+
class CreditAccountsServiceV310 extends SDKConstruct {
|
|
45
|
+
#compressor;
|
|
46
|
+
#batchSize;
|
|
47
|
+
constructor(sdk, options) {
|
|
48
|
+
super(sdk);
|
|
49
|
+
this.#batchSize = options?.batchSize;
|
|
50
|
+
}
|
|
51
|
+
setBatchSize(batchSize) {
|
|
52
|
+
this.#batchSize = batchSize;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* {@inheritDoc ICreditAccountsService.getCreditAccountData}
|
|
56
|
+
**/
|
|
57
|
+
async getCreditAccountData(account, blockNumber) {
|
|
58
|
+
let raw;
|
|
59
|
+
try {
|
|
60
|
+
raw = await this.client.readContract({
|
|
61
|
+
abi: creditAccountCompressorAbi,
|
|
62
|
+
address: this.compressor,
|
|
63
|
+
functionName: "getCreditAccountData",
|
|
64
|
+
args: [account],
|
|
65
|
+
blockNumber,
|
|
66
|
+
// @ts-expect-error
|
|
67
|
+
gas: this.sdk.gasLimit
|
|
68
|
+
});
|
|
69
|
+
} catch (_e) {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
const marketSuite = this.sdk.marketRegister.findByCreditManager(
|
|
73
|
+
raw.creditManager
|
|
74
|
+
);
|
|
75
|
+
const factory = marketSuite.kycFactory;
|
|
76
|
+
let ca;
|
|
77
|
+
let investor;
|
|
78
|
+
if (raw.success) {
|
|
79
|
+
ca = raw;
|
|
80
|
+
investor = await factory?.getInvestor(raw.creditAccount, false);
|
|
81
|
+
} else {
|
|
82
|
+
const { txs: priceUpdateTxs } = await this.#getUpdateForAccount(raw);
|
|
83
|
+
[ca, investor] = await simulateWithPriceUpdates(this.client, {
|
|
84
|
+
priceUpdates: priceUpdateTxs,
|
|
85
|
+
contracts: [
|
|
86
|
+
{
|
|
87
|
+
abi: creditAccountCompressorAbi,
|
|
88
|
+
address: this.compressor,
|
|
89
|
+
functionName: "getCreditAccountData",
|
|
90
|
+
args: [account]
|
|
91
|
+
},
|
|
92
|
+
...factory ? [
|
|
93
|
+
{
|
|
94
|
+
abi: iKYCFactoryAbi,
|
|
95
|
+
address: factory.address,
|
|
96
|
+
functionName: "getInvestor",
|
|
97
|
+
args: [raw.creditAccount]
|
|
98
|
+
}
|
|
99
|
+
] : []
|
|
100
|
+
],
|
|
101
|
+
blockNumber,
|
|
102
|
+
gas: this.sdk.gasLimit
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return { ...ca, investor };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* {@inheritDoc ICreditAccountsService.getCreditAccounts}
|
|
109
|
+
**/
|
|
110
|
+
async getCreditAccounts(options, blockNumber) {
|
|
111
|
+
const {
|
|
112
|
+
creditManager,
|
|
113
|
+
includeZeroDebt = false,
|
|
114
|
+
maxHealthFactor = MAX_UINT256,
|
|
115
|
+
minHealthFactor = 0n,
|
|
116
|
+
owner = ADDRESS_0X0,
|
|
117
|
+
ignoreReservePrices = false
|
|
118
|
+
} = options ?? {};
|
|
119
|
+
const arg0 = creditManager ?? {
|
|
120
|
+
configurators: this.marketConfigurators,
|
|
121
|
+
creditManagers: [],
|
|
122
|
+
pools: [],
|
|
123
|
+
underlying: ADDRESS_0X0
|
|
124
|
+
};
|
|
125
|
+
const caFilter = {
|
|
126
|
+
owner,
|
|
127
|
+
includeZeroDebt,
|
|
128
|
+
minHealthFactor,
|
|
129
|
+
maxHealthFactor,
|
|
130
|
+
reverting: false
|
|
131
|
+
};
|
|
132
|
+
const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
|
|
133
|
+
ignoreReservePrices ? { main: true } : void 0
|
|
134
|
+
);
|
|
135
|
+
const allCAs = [];
|
|
136
|
+
let revertingOffset = 0;
|
|
137
|
+
for (const reverting of [false, true]) {
|
|
138
|
+
let offset = 0n;
|
|
139
|
+
revertingOffset = allCAs.length;
|
|
140
|
+
do {
|
|
141
|
+
const [accounts, newOffset] = await this.#getCreditAccounts(
|
|
142
|
+
this.#batchSize ? [
|
|
143
|
+
arg0,
|
|
144
|
+
{ ...caFilter, reverting },
|
|
145
|
+
offset,
|
|
146
|
+
BigInt(this.#batchSize)
|
|
147
|
+
// limit
|
|
148
|
+
] : [arg0, { ...caFilter, reverting }, offset],
|
|
149
|
+
priceUpdateTxs,
|
|
150
|
+
blockNumber
|
|
151
|
+
);
|
|
152
|
+
allCAs.push(...accounts);
|
|
153
|
+
offset = newOffset;
|
|
154
|
+
} while (offset !== 0n);
|
|
155
|
+
}
|
|
156
|
+
this.logger?.debug(
|
|
157
|
+
`loaded ${allCAs.length} credit accounts (${allCAs.length - revertingOffset} reverting)`
|
|
158
|
+
);
|
|
159
|
+
return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* {@inheritDoc ICreditAccountsService.getBorrowerCreditAccounts}
|
|
163
|
+
**/
|
|
164
|
+
async getBorrowerCreditAccounts(borrower, options, blockNumber) {
|
|
165
|
+
const {
|
|
166
|
+
creditManager,
|
|
167
|
+
includeZeroDebt = false,
|
|
168
|
+
maxHealthFactor = MAX_UINT256,
|
|
169
|
+
minHealthFactor = 0n,
|
|
170
|
+
ignoreReservePrices = false
|
|
171
|
+
} = options ?? {};
|
|
172
|
+
const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
|
|
173
|
+
ignoreReservePrices ? { main: true } : void 0
|
|
174
|
+
);
|
|
175
|
+
const investorDataList = await this.sdk.kyc.getInvestorData(borrower);
|
|
176
|
+
const kycAccountAddresses = investorDataList.flatMap(
|
|
177
|
+
(d) => d.creditAccounts.map((ca) => ca.creditAccount)
|
|
178
|
+
);
|
|
179
|
+
const cmFilter = creditManager ? {
|
|
180
|
+
configurators: [],
|
|
181
|
+
creditManagers: [creditManager],
|
|
182
|
+
pools: [],
|
|
183
|
+
underlying: ADDRESS_0X0
|
|
184
|
+
} : {
|
|
185
|
+
configurators: this.marketConfigurators,
|
|
186
|
+
creditManagers: [],
|
|
187
|
+
pools: [],
|
|
188
|
+
underlying: ADDRESS_0X0
|
|
189
|
+
};
|
|
190
|
+
const permissiveFilter = {
|
|
191
|
+
owner: borrower,
|
|
192
|
+
includeZeroDebt: true,
|
|
193
|
+
minHealthFactor: 0n,
|
|
194
|
+
maxHealthFactor: MAX_UINT256,
|
|
195
|
+
reverting: false
|
|
196
|
+
};
|
|
197
|
+
const kycContracts = kycAccountAddresses.map(
|
|
198
|
+
(account) => ({
|
|
199
|
+
abi: creditAccountCompressorAbi,
|
|
200
|
+
address: this.compressor,
|
|
201
|
+
functionName: "getCreditAccountData",
|
|
202
|
+
args: [account]
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
const getCreditAccountsContracts = [false, true].map(
|
|
206
|
+
(reverting) => ({
|
|
207
|
+
abi: creditAccountCompressorAbi,
|
|
208
|
+
address: this.compressor,
|
|
209
|
+
functionName: "getCreditAccounts",
|
|
210
|
+
args: [cmFilter, { ...permissiveFilter, reverting }, 0n]
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
const allContracts = [...kycContracts, ...getCreditAccountsContracts];
|
|
214
|
+
const results = await simulateWithPriceUpdates(this.client, {
|
|
215
|
+
priceUpdates: priceUpdateTxs,
|
|
216
|
+
contracts: allContracts,
|
|
217
|
+
blockNumber,
|
|
218
|
+
gas: this.sdk.gasLimit
|
|
219
|
+
});
|
|
220
|
+
const kycResults = results.slice(
|
|
221
|
+
0,
|
|
222
|
+
kycAccountAddresses.length
|
|
223
|
+
);
|
|
224
|
+
const normalResults = results.slice(kycAccountAddresses.length);
|
|
225
|
+
const seen = new AddressSet();
|
|
226
|
+
const allCAs = [];
|
|
227
|
+
for (const ca of kycResults) {
|
|
228
|
+
if (!seen.has(ca.creditAccount)) {
|
|
229
|
+
seen.add(ca.creditAccount);
|
|
230
|
+
allCAs.push({ ...ca, investor: borrower });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const [accounts] of normalResults) {
|
|
234
|
+
for (const ca of accounts) {
|
|
235
|
+
if (!seen.has(ca.creditAccount)) {
|
|
236
|
+
seen.add(ca.creditAccount);
|
|
237
|
+
allCAs.push({ ...ca, investor: void 0 });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const filtered = allCAs.filter((ca) => {
|
|
242
|
+
if (!includeZeroDebt && ca.debt === 0n) return false;
|
|
243
|
+
if (ca.healthFactor < minHealthFactor) return false;
|
|
244
|
+
if (ca.healthFactor > maxHealthFactor) return false;
|
|
245
|
+
if (creditManager && !hexEq(ca.creditManager, creditManager))
|
|
246
|
+
return false;
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
this.logger?.debug(
|
|
250
|
+
`loaded ${allCAs.length} borrower credit accounts (${kycResults.length} KYC, ${filtered.length} after filter)`
|
|
251
|
+
);
|
|
252
|
+
return filtered.sort((a, b) => Number(a.healthFactor - b.healthFactor));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* {@inheritDoc ICreditAccountsService.getRewards}
|
|
256
|
+
**/
|
|
257
|
+
async getRewards(creditAccount) {
|
|
258
|
+
const rewards = await this.client.readContract({
|
|
259
|
+
abi: rewardsCompressorAbi,
|
|
260
|
+
address: this.rewardCompressor,
|
|
261
|
+
functionName: "getRewards",
|
|
262
|
+
args: [creditAccount]
|
|
263
|
+
});
|
|
264
|
+
const callData = encodeFunctionData({
|
|
265
|
+
abi: iBaseRewardPoolAbi,
|
|
266
|
+
functionName: "getReward",
|
|
267
|
+
args: []
|
|
268
|
+
});
|
|
269
|
+
const r = rewards.reduce((acc, r2) => {
|
|
270
|
+
const adapter = r2.adapter.toLowerCase();
|
|
271
|
+
const stakedPhantomToken = r2.stakedPhantomToken.toLowerCase();
|
|
272
|
+
const rewardToken = r2.rewardToken.toLowerCase();
|
|
273
|
+
const key = [adapter, stakedPhantomToken].join("-");
|
|
274
|
+
if (!acc[key]) {
|
|
275
|
+
acc[key] = {
|
|
276
|
+
adapter,
|
|
277
|
+
stakedPhantomToken,
|
|
278
|
+
calls: [
|
|
279
|
+
{
|
|
280
|
+
target: adapter,
|
|
281
|
+
callData
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
rewards: []
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
acc[key].rewards.push({
|
|
288
|
+
token: rewardToken,
|
|
289
|
+
balance: r2.amount
|
|
290
|
+
});
|
|
291
|
+
return acc;
|
|
292
|
+
}, {});
|
|
293
|
+
return Object.values(r);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* {@inheritDoc ICreditAccountsService.getConnectedBots}
|
|
297
|
+
**/
|
|
298
|
+
async getConnectedBots(accountsToCheck, legacyMigrationBot, additionalBots) {
|
|
299
|
+
const allResp = await this.client.multicall({
|
|
300
|
+
contracts: [
|
|
301
|
+
...accountsToCheck.map((o) => {
|
|
302
|
+
const pool = this.sdk.marketRegister.findByCreditManager(
|
|
303
|
+
o.creditManager
|
|
304
|
+
);
|
|
305
|
+
return {
|
|
306
|
+
abi: peripheryCompressorAbi,
|
|
307
|
+
address: this.peripheryCompressor,
|
|
308
|
+
functionName: "getConnectedBots",
|
|
309
|
+
args: [pool.configurator.address, o.creditAccount]
|
|
310
|
+
};
|
|
311
|
+
}),
|
|
312
|
+
...legacyMigrationBot ? accountsToCheck.map((ca) => {
|
|
313
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
314
|
+
ca.creditManager
|
|
315
|
+
);
|
|
316
|
+
return {
|
|
317
|
+
abi: iBotListV310Abi,
|
|
318
|
+
address: cm.creditFacade.botList,
|
|
319
|
+
functionName: "getBotStatus",
|
|
320
|
+
args: [legacyMigrationBot, ca.creditAccount]
|
|
321
|
+
};
|
|
322
|
+
}) : [],
|
|
323
|
+
...accountsToCheck.flatMap((ca) => {
|
|
324
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
325
|
+
ca.creditManager
|
|
326
|
+
);
|
|
327
|
+
return additionalBots.map((bot) => {
|
|
328
|
+
return {
|
|
329
|
+
abi: iBotListV310Abi,
|
|
330
|
+
address: cm.creditFacade.botList,
|
|
331
|
+
functionName: "getBotStatus",
|
|
332
|
+
args: [bot, ca.creditAccount]
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
})
|
|
336
|
+
],
|
|
337
|
+
allowFailure: true,
|
|
338
|
+
batchSize: 0
|
|
339
|
+
});
|
|
340
|
+
const legacyStart = 0;
|
|
341
|
+
const legacyEnd = accountsToCheck.length;
|
|
342
|
+
const legacy = allResp.slice(
|
|
343
|
+
legacyStart,
|
|
344
|
+
legacyEnd
|
|
345
|
+
);
|
|
346
|
+
const migrationStart = legacyEnd;
|
|
347
|
+
const migrationEnd = legacyMigrationBot ? migrationStart + accountsToCheck.length : migrationStart;
|
|
348
|
+
const migrationResp = allResp.slice(
|
|
349
|
+
migrationStart,
|
|
350
|
+
migrationEnd
|
|
351
|
+
);
|
|
352
|
+
const additionalStart = migrationEnd;
|
|
353
|
+
const additionalResp = allResp.slice(
|
|
354
|
+
additionalStart
|
|
355
|
+
);
|
|
356
|
+
return {
|
|
357
|
+
legacy,
|
|
358
|
+
additionalBots: this.#getActiveBots(
|
|
359
|
+
accountsToCheck,
|
|
360
|
+
additionalBots,
|
|
361
|
+
additionalResp
|
|
362
|
+
),
|
|
363
|
+
legacyMigration: this.#getActiveMigrationBots(
|
|
364
|
+
accountsToCheck,
|
|
365
|
+
legacyMigrationBot,
|
|
366
|
+
migrationResp
|
|
367
|
+
)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
#getActiveBots(accountsToCheck, bots, result) {
|
|
371
|
+
if (result.length !== bots.length * accountsToCheck.length) {
|
|
372
|
+
console.error(
|
|
373
|
+
"result length mismatch",
|
|
374
|
+
result.length,
|
|
375
|
+
bots.length * accountsToCheck.length
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const botsByCAIndex = accountsToCheck.reduce((acc, _, index) => {
|
|
379
|
+
const r = result.slice(index * bots.length, (index + 1) * bots.length);
|
|
380
|
+
acc.push({
|
|
381
|
+
result: r
|
|
382
|
+
});
|
|
383
|
+
return acc;
|
|
384
|
+
}, []);
|
|
385
|
+
return botsByCAIndex;
|
|
386
|
+
}
|
|
387
|
+
#getActiveMigrationBots(accountsToCheck, bot, result) {
|
|
388
|
+
if (bot) {
|
|
389
|
+
if (result.length !== accountsToCheck.length) {
|
|
390
|
+
console.error(
|
|
391
|
+
"result length mismatch for migration bots",
|
|
392
|
+
result.length,
|
|
393
|
+
accountsToCheck.length
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return { result, botAddress: bot };
|
|
397
|
+
}
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* {@inheritDoc ICreditAccountsService.fullyLiquidate}
|
|
402
|
+
**/
|
|
403
|
+
async fullyLiquidate(props) {
|
|
404
|
+
const {
|
|
405
|
+
account,
|
|
406
|
+
to,
|
|
407
|
+
slippage = 50n,
|
|
408
|
+
keepAssets,
|
|
409
|
+
ignoreReservePrices,
|
|
410
|
+
applyLossPolicy,
|
|
411
|
+
debtOnly
|
|
412
|
+
} = props;
|
|
413
|
+
const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
|
|
414
|
+
const routerCloseResult = await this.sdk.routerFor(account).findBestClosePath({
|
|
415
|
+
creditAccount: account,
|
|
416
|
+
creditManager: cm.creditManager,
|
|
417
|
+
slippage,
|
|
418
|
+
keepAssets,
|
|
419
|
+
debtOnly
|
|
420
|
+
});
|
|
421
|
+
const calls = await this.#prependPriceUpdates(
|
|
422
|
+
account.creditManager,
|
|
423
|
+
routerCloseResult.calls,
|
|
424
|
+
account,
|
|
425
|
+
{ ignoreReservePrices }
|
|
426
|
+
);
|
|
427
|
+
let lossPolicyData;
|
|
428
|
+
if (applyLossPolicy) {
|
|
429
|
+
const market = this.sdk.marketRegister.findByCreditManager(
|
|
430
|
+
account.creditManager
|
|
431
|
+
);
|
|
432
|
+
lossPolicyData = await market.lossPolicy.getLiquidationData(
|
|
433
|
+
account.creditAccount
|
|
434
|
+
);
|
|
435
|
+
this.logger?.debug({ lossPolicyData }, "loss policy data");
|
|
436
|
+
}
|
|
437
|
+
const tx = cm.creditFacade.liquidateCreditAccount(
|
|
438
|
+
account.creditAccount,
|
|
439
|
+
to,
|
|
440
|
+
calls,
|
|
441
|
+
lossPolicyData
|
|
442
|
+
);
|
|
443
|
+
return {
|
|
444
|
+
tx,
|
|
445
|
+
calls,
|
|
446
|
+
routerCloseResult,
|
|
447
|
+
lossPolicyData,
|
|
448
|
+
creditFacade: cm.creditFacade
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* {@inheritDoc ICreditAccountsService.calcMinSeizedAmount}
|
|
453
|
+
*/
|
|
454
|
+
calcMinSeizedAmount(props) {
|
|
455
|
+
const { account, token, repaidAmount } = props;
|
|
456
|
+
const market = this.sdk.marketRegister.findByCreditManager(
|
|
457
|
+
account.creditManager
|
|
458
|
+
);
|
|
459
|
+
const suite = this.sdk.marketRegister.findCreditManager(
|
|
460
|
+
account.creditManager
|
|
461
|
+
);
|
|
462
|
+
const fee = suite.isExpired ? suite.creditManager.liquidationDiscountExpired : suite.creditManager.liquidationDiscount;
|
|
463
|
+
const tokenAmount = market.priceOracle.convert(
|
|
464
|
+
market.underlying,
|
|
465
|
+
token,
|
|
466
|
+
repaidAmount
|
|
467
|
+
);
|
|
468
|
+
return tokenAmount * 9990n / BigInt(fee);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* {@inheritDoc ICreditAccountsService.partiallyLiquidate}
|
|
472
|
+
*/
|
|
473
|
+
async partiallyLiquidate(props) {
|
|
474
|
+
const { account, token, repaidAmount, minSeizedAmount, to } = props;
|
|
475
|
+
const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
|
|
476
|
+
const updates = await this.getOnDemandPriceUpdates(account, true);
|
|
477
|
+
const tx = cm.creditFacade.partiallyLiquidateCreditAccount(
|
|
478
|
+
account.creditAccount,
|
|
479
|
+
token,
|
|
480
|
+
repaidAmount,
|
|
481
|
+
minSeizedAmount,
|
|
482
|
+
to,
|
|
483
|
+
updates
|
|
484
|
+
);
|
|
485
|
+
return tx;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* {@inheritDoc ICreditAccountsService.closeCreditAccount}
|
|
489
|
+
**/
|
|
490
|
+
async closeCreditAccount({
|
|
491
|
+
operation,
|
|
492
|
+
assetsToWithdraw,
|
|
493
|
+
creditAccount: ca,
|
|
494
|
+
to,
|
|
495
|
+
slippage = 50n,
|
|
496
|
+
closePath
|
|
497
|
+
}) {
|
|
498
|
+
const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
|
|
499
|
+
await this.sdk.tokensMeta.loadTokenData(cm.underlying);
|
|
500
|
+
const underlying = this.sdk.tokensMeta.mustGet(cm.underlying);
|
|
501
|
+
if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
"closeCreditAccount is not supported for KYC underlying credit accounts"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const routerCloseResult = closePath || await this.sdk.routerFor(ca).findBestClosePath({
|
|
507
|
+
creditAccount: ca,
|
|
508
|
+
creditManager: cm.creditManager,
|
|
509
|
+
slippage
|
|
510
|
+
});
|
|
511
|
+
const operationCalls = [
|
|
512
|
+
...routerCloseResult.calls,
|
|
513
|
+
...this.#prepareDisableQuotas(ca),
|
|
514
|
+
...this.#prepareDecreaseDebt(ca),
|
|
515
|
+
...assetsToWithdraw.map(
|
|
516
|
+
(t) => this.#prepareWithdrawToken(ca.creditFacade, t, MAX_UINT256, to)
|
|
517
|
+
)
|
|
518
|
+
];
|
|
519
|
+
const calls = operation === "close" ? operationCalls : await this.#prependPriceUpdates(ca.creditManager, operationCalls, ca);
|
|
520
|
+
const tx = await this.#closeCreditAccountTx(
|
|
521
|
+
cm,
|
|
522
|
+
ca.creditAccount,
|
|
523
|
+
calls,
|
|
524
|
+
operation
|
|
525
|
+
);
|
|
526
|
+
return { tx, calls, routerCloseResult, creditFacade: cm.creditFacade };
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* {@inheritDoc ICreditAccountsService.updateQuotas}
|
|
530
|
+
**/
|
|
531
|
+
async updateQuotas({
|
|
532
|
+
minQuota,
|
|
533
|
+
averageQuota,
|
|
534
|
+
creditAccount
|
|
535
|
+
}) {
|
|
536
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
537
|
+
creditAccount.creditManager
|
|
538
|
+
);
|
|
539
|
+
const operationCalls = this.#prepareUpdateQuotas(
|
|
540
|
+
creditAccount.creditFacade,
|
|
541
|
+
{ minQuota, averageQuota }
|
|
542
|
+
);
|
|
543
|
+
const calls = await this.#prependPriceUpdates(
|
|
544
|
+
creditAccount.creditManager,
|
|
545
|
+
operationCalls,
|
|
546
|
+
creditAccount
|
|
547
|
+
);
|
|
548
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
549
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* {@inheritDoc ICreditAccountsService.addCollateral}
|
|
553
|
+
**/
|
|
554
|
+
async addCollateral({
|
|
555
|
+
creditAccount,
|
|
556
|
+
asset,
|
|
557
|
+
permit,
|
|
558
|
+
ethAmount,
|
|
559
|
+
minQuota,
|
|
560
|
+
averageQuota
|
|
561
|
+
}) {
|
|
562
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
563
|
+
creditAccount.creditManager
|
|
564
|
+
);
|
|
565
|
+
const operationCalls = [
|
|
566
|
+
...this.#prepareAddCollateral(
|
|
567
|
+
creditAccount.creditFacade,
|
|
568
|
+
[asset],
|
|
569
|
+
permit ? { [asset.token]: permit } : {}
|
|
570
|
+
),
|
|
571
|
+
...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
|
|
572
|
+
minQuota,
|
|
573
|
+
averageQuota
|
|
574
|
+
})
|
|
575
|
+
];
|
|
576
|
+
const calls = await this.#prependPriceUpdates(
|
|
577
|
+
creditAccount.creditManager,
|
|
578
|
+
operationCalls,
|
|
579
|
+
creditAccount
|
|
580
|
+
);
|
|
581
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
582
|
+
tx.value = ethAmount.toString(10);
|
|
583
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* {@inheritDoc ICreditAccountsService.changeDebt}
|
|
587
|
+
**/
|
|
588
|
+
async changeDebt({
|
|
589
|
+
creditAccount,
|
|
590
|
+
amount,
|
|
591
|
+
collateral
|
|
592
|
+
}) {
|
|
593
|
+
if (amount === 0n) {
|
|
594
|
+
throw new Error("debt increase or decrease must be non-zero");
|
|
595
|
+
}
|
|
596
|
+
const isDecrease = amount < 0n;
|
|
597
|
+
const change = amount > 0n ? amount : -amount;
|
|
598
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
599
|
+
creditAccount.creditManager
|
|
600
|
+
);
|
|
601
|
+
const addCollateralCalls = collateral && isDecrease ? this.#prepareAddCollateral(
|
|
602
|
+
creditAccount.creditFacade,
|
|
603
|
+
[
|
|
604
|
+
{
|
|
605
|
+
token: collateral[0].token,
|
|
606
|
+
balance: collateral[0].balance
|
|
607
|
+
}
|
|
608
|
+
],
|
|
609
|
+
{}
|
|
610
|
+
) : [];
|
|
611
|
+
const unwrapCalls = collateral && isDecrease ? await this.getKYCUnwrapCalls(
|
|
612
|
+
collateral[0].balance,
|
|
613
|
+
creditAccount.creditManager
|
|
614
|
+
) || [] : [];
|
|
615
|
+
if (addCollateralCalls.length > 0 && unwrapCalls.length === 0 && collateral && collateral?.[0].token !== creditAccount.underlying) {
|
|
616
|
+
throw new Error(
|
|
617
|
+
"Can't use collateral other than underlying for non KYC market"
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const operationCalls = [
|
|
621
|
+
...addCollateralCalls,
|
|
622
|
+
...unwrapCalls,
|
|
623
|
+
this.#prepareChangeDebt(creditAccount.creditFacade, change, isDecrease)
|
|
624
|
+
];
|
|
625
|
+
const calls = await this.#prependPriceUpdates(
|
|
626
|
+
creditAccount.creditManager,
|
|
627
|
+
operationCalls,
|
|
628
|
+
creditAccount
|
|
629
|
+
);
|
|
630
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
631
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* {@inheritDoc ICreditAccountsService.executeSwap}
|
|
635
|
+
**/
|
|
636
|
+
async executeSwap({
|
|
637
|
+
creditAccount,
|
|
638
|
+
calls: swapCalls,
|
|
639
|
+
minQuota,
|
|
640
|
+
averageQuota
|
|
641
|
+
}) {
|
|
642
|
+
if (swapCalls.length === 0) throw new Error("No path to execute");
|
|
643
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
644
|
+
creditAccount.creditManager
|
|
645
|
+
);
|
|
646
|
+
const operationCalls = [
|
|
647
|
+
...swapCalls,
|
|
648
|
+
...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
|
|
649
|
+
minQuota,
|
|
650
|
+
averageQuota
|
|
651
|
+
})
|
|
652
|
+
];
|
|
653
|
+
const calls = await this.#prependPriceUpdates(
|
|
654
|
+
creditAccount.creditManager,
|
|
655
|
+
operationCalls,
|
|
656
|
+
creditAccount
|
|
657
|
+
);
|
|
658
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
659
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* {@inheritDoc ICreditAccountsService.previewDelayedWithdrawal}
|
|
663
|
+
**/
|
|
664
|
+
async previewDelayedWithdrawal({
|
|
665
|
+
creditAccount,
|
|
666
|
+
amount,
|
|
667
|
+
token
|
|
668
|
+
}) {
|
|
669
|
+
const compressor = getWithdrawalCompressorAddress(this.sdk.chainId);
|
|
670
|
+
if (!compressor)
|
|
671
|
+
throw new Error(
|
|
672
|
+
`No compressor for current chain ${this.sdk.networkType}`
|
|
673
|
+
);
|
|
674
|
+
const contract = getContract({
|
|
675
|
+
address: compressor,
|
|
676
|
+
abi: iWithdrawalCompressorV310Abi,
|
|
677
|
+
client: this.client
|
|
678
|
+
});
|
|
679
|
+
const resp = await contract.read.getWithdrawalRequestResult([
|
|
680
|
+
creditAccount,
|
|
681
|
+
token,
|
|
682
|
+
amount
|
|
683
|
+
]);
|
|
684
|
+
return resp;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* {@inheritDoc ICreditAccountsService.getPendingWithdrawals}
|
|
688
|
+
**/
|
|
689
|
+
async getPendingWithdrawals({
|
|
690
|
+
creditAccount
|
|
691
|
+
}) {
|
|
692
|
+
const compressor = getWithdrawalCompressorAddress(this.sdk.chainId);
|
|
693
|
+
if (!compressor)
|
|
694
|
+
throw new Error(
|
|
695
|
+
`No compressor for current chain ${this.sdk.networkType}`
|
|
696
|
+
);
|
|
697
|
+
const contract = getContract({
|
|
698
|
+
address: compressor,
|
|
699
|
+
abi: iWithdrawalCompressorV310Abi,
|
|
700
|
+
client: this.client
|
|
701
|
+
});
|
|
702
|
+
const resp = await contract.read.getCurrentWithdrawals([creditAccount]);
|
|
703
|
+
const claimableNow = resp?.[0] || [];
|
|
704
|
+
const pendingResult = [...resp?.[1] || []].sort(
|
|
705
|
+
(a, b) => a.claimableAt < b.claimableAt ? -1 : 1
|
|
706
|
+
);
|
|
707
|
+
const respResult = {
|
|
708
|
+
claimableNow: [...claimableNow],
|
|
709
|
+
pending: pendingResult
|
|
710
|
+
};
|
|
711
|
+
return respResult;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* {@inheritDoc ICreditAccountsService.startDelayedWithdrawal}
|
|
715
|
+
**/
|
|
716
|
+
async startDelayedWithdrawal({
|
|
717
|
+
creditAccount,
|
|
718
|
+
minQuota,
|
|
719
|
+
averageQuota,
|
|
720
|
+
preview
|
|
721
|
+
}) {
|
|
722
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
723
|
+
creditAccount.creditManager
|
|
724
|
+
);
|
|
725
|
+
const record = preview.outputs.reduce((acc, o) => {
|
|
726
|
+
const token = o.token.toLowerCase();
|
|
727
|
+
acc[token] = (acc[token] || 0n) + o.amount;
|
|
728
|
+
return acc;
|
|
729
|
+
}, {});
|
|
730
|
+
const balances = Object.entries(record).filter(([, a]) => a > 10n);
|
|
731
|
+
const storeExpectedBalances = {
|
|
732
|
+
target: cm.creditFacade.address,
|
|
733
|
+
callData: encodeFunctionData({
|
|
734
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
735
|
+
functionName: "storeExpectedBalances",
|
|
736
|
+
args: [
|
|
737
|
+
balances.map(([token, amount]) => ({
|
|
738
|
+
token,
|
|
739
|
+
amount: amount > 10n ? amount - 10n : 0n
|
|
740
|
+
}))
|
|
741
|
+
]
|
|
742
|
+
})
|
|
743
|
+
};
|
|
744
|
+
const compareBalances = {
|
|
745
|
+
target: cm.creditFacade.address,
|
|
746
|
+
callData: encodeFunctionData({
|
|
747
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
748
|
+
functionName: "compareBalances",
|
|
749
|
+
args: []
|
|
750
|
+
})
|
|
751
|
+
};
|
|
752
|
+
const operationCalls = [
|
|
753
|
+
storeExpectedBalances,
|
|
754
|
+
...preview.requestCalls,
|
|
755
|
+
compareBalances,
|
|
756
|
+
...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
|
|
757
|
+
minQuota,
|
|
758
|
+
averageQuota
|
|
759
|
+
})
|
|
760
|
+
];
|
|
761
|
+
const calls = await this.#prependPriceUpdates(
|
|
762
|
+
creditAccount.creditManager,
|
|
763
|
+
operationCalls,
|
|
764
|
+
creditAccount
|
|
765
|
+
);
|
|
766
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
767
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* {@inheritDoc ICreditAccountsService.claimDelayed}
|
|
771
|
+
**/
|
|
772
|
+
async claimDelayed({
|
|
773
|
+
creditAccount,
|
|
774
|
+
minQuota,
|
|
775
|
+
averageQuota,
|
|
776
|
+
claimableNow
|
|
777
|
+
}) {
|
|
778
|
+
const zeroDebt = creditAccount.debt === 0n;
|
|
779
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
780
|
+
creditAccount.creditManager
|
|
781
|
+
);
|
|
782
|
+
const record = claimableNow.outputs.reduce(
|
|
783
|
+
(acc, o) => {
|
|
784
|
+
const token = o.token.toLowerCase();
|
|
785
|
+
acc[token] = (acc[token] || 0n) + o.amount;
|
|
786
|
+
return acc;
|
|
787
|
+
},
|
|
788
|
+
{}
|
|
789
|
+
);
|
|
790
|
+
const balances = Object.entries(record).filter(([, a]) => a > 10n);
|
|
791
|
+
const storeExpectedBalances = {
|
|
792
|
+
target: cm.creditFacade.address,
|
|
793
|
+
callData: encodeFunctionData({
|
|
794
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
795
|
+
functionName: "storeExpectedBalances",
|
|
796
|
+
args: [
|
|
797
|
+
balances.map(([token, amount]) => ({
|
|
798
|
+
token,
|
|
799
|
+
amount: amount > 10n ? amount - 10n : 0n
|
|
800
|
+
}))
|
|
801
|
+
]
|
|
802
|
+
})
|
|
803
|
+
};
|
|
804
|
+
const compareBalances = {
|
|
805
|
+
target: cm.creditFacade.address,
|
|
806
|
+
callData: encodeFunctionData({
|
|
807
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
808
|
+
functionName: "compareBalances",
|
|
809
|
+
args: []
|
|
810
|
+
})
|
|
811
|
+
};
|
|
812
|
+
const quotaCalls = zeroDebt ? [] : this.#prepareUpdateQuotas(creditAccount.creditFacade, {
|
|
813
|
+
minQuota,
|
|
814
|
+
averageQuota
|
|
815
|
+
});
|
|
816
|
+
const operationCalls = [
|
|
817
|
+
storeExpectedBalances,
|
|
818
|
+
...claimableNow.claimCalls,
|
|
819
|
+
compareBalances,
|
|
820
|
+
...quotaCalls
|
|
821
|
+
];
|
|
822
|
+
const calls = zeroDebt ? operationCalls : await this.#prependPriceUpdates(
|
|
823
|
+
creditAccount.creditManager,
|
|
824
|
+
operationCalls,
|
|
825
|
+
creditAccount
|
|
826
|
+
);
|
|
827
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
828
|
+
return { tx, calls, creditFacade: cm.creditFacade };
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* {@inheritDoc ICreditAccountsService.getApprovalAddress}
|
|
832
|
+
**/
|
|
833
|
+
async getApprovalAddress(options) {
|
|
834
|
+
const { creditManager } = options;
|
|
835
|
+
const suite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
836
|
+
const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
|
|
837
|
+
const factory = marketSuite.kycFactory;
|
|
838
|
+
if (factory) {
|
|
839
|
+
return factory.getApprovalAddress(options);
|
|
840
|
+
}
|
|
841
|
+
return suite.creditManager.address;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* {@inheritDoc ICreditAccountsService.getOpenAccountRequirements}
|
|
845
|
+
*/
|
|
846
|
+
async getOpenAccountRequirements(borrower, creditManager, props) {
|
|
847
|
+
const { kycFactory } = this.sdk.marketRegister.findByCreditManager(creditManager);
|
|
848
|
+
if (!kycFactory) {
|
|
849
|
+
return void 0;
|
|
850
|
+
}
|
|
851
|
+
return kycFactory.getOpenAccountRequirements(borrower, props);
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* {@inheritDoc ICreditAccountsService.openCA}
|
|
855
|
+
**/
|
|
856
|
+
async openCA(props) {
|
|
857
|
+
const {
|
|
858
|
+
ethAmount,
|
|
859
|
+
creditManager,
|
|
860
|
+
reopenCreditAccount,
|
|
861
|
+
collateral,
|
|
862
|
+
permits,
|
|
863
|
+
debt,
|
|
864
|
+
withdrawToken,
|
|
865
|
+
referralCode,
|
|
866
|
+
to,
|
|
867
|
+
calls: openPathCalls,
|
|
868
|
+
callsAfter,
|
|
869
|
+
minQuota,
|
|
870
|
+
averageQuota,
|
|
871
|
+
kycOptions
|
|
872
|
+
} = props;
|
|
873
|
+
const cmSuite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
874
|
+
const cm = cmSuite.creditManager;
|
|
875
|
+
let tokenToWithdraw;
|
|
876
|
+
if (withdrawToken === true) {
|
|
877
|
+
tokenToWithdraw = cm.underlying;
|
|
878
|
+
} else if (typeof withdrawToken === "string") {
|
|
879
|
+
tokenToWithdraw = withdrawToken;
|
|
880
|
+
}
|
|
881
|
+
const operationCalls = [
|
|
882
|
+
this.#prepareIncreaseDebt(cm.creditFacade, debt),
|
|
883
|
+
...this.#prepareAddCollateral(cm.creditFacade, collateral, permits),
|
|
884
|
+
...openPathCalls,
|
|
885
|
+
// path from underlying to withdrawal token
|
|
886
|
+
...tokenToWithdraw ? [
|
|
887
|
+
this.#prepareWithdrawToken(
|
|
888
|
+
cm.creditFacade,
|
|
889
|
+
tokenToWithdraw,
|
|
890
|
+
MAX_UINT256,
|
|
891
|
+
to
|
|
892
|
+
)
|
|
893
|
+
] : [],
|
|
894
|
+
...this.#prepareUpdateQuotas(cm.creditFacade, {
|
|
895
|
+
minQuota,
|
|
896
|
+
averageQuota
|
|
897
|
+
}),
|
|
898
|
+
...callsAfter ?? []
|
|
899
|
+
];
|
|
900
|
+
const calls = await this.#prependPriceUpdates(cm.address, operationCalls);
|
|
901
|
+
let tx;
|
|
902
|
+
if (reopenCreditAccount) {
|
|
903
|
+
tx = await this.#multicallTx(cmSuite, reopenCreditAccount, calls);
|
|
904
|
+
} else {
|
|
905
|
+
tx = await this.#openCreditAccountTx(
|
|
906
|
+
cmSuite,
|
|
907
|
+
to,
|
|
908
|
+
calls,
|
|
909
|
+
referralCode,
|
|
910
|
+
kycOptions
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
tx.value = ethAmount.toString(10);
|
|
914
|
+
return { calls, tx, creditFacade: cmSuite.creditFacade };
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* {@inheritDoc ICreditAccountsService.getBorrowRate}
|
|
918
|
+
**/
|
|
919
|
+
getBorrowRate(ca) {
|
|
920
|
+
const { creditManager } = this.sdk.marketRegister.findCreditManager(
|
|
921
|
+
ca.creditManager
|
|
922
|
+
);
|
|
923
|
+
const { pool } = this.sdk.marketRegister.findByCreditManager(
|
|
924
|
+
ca.creditManager
|
|
925
|
+
);
|
|
926
|
+
const { feeInterest } = creditManager;
|
|
927
|
+
const { baseInterestRate } = pool.pool;
|
|
928
|
+
const baseRateWithFee = baseInterestRate * (BigInt(feeInterest) + PERCENTAGE_FACTOR);
|
|
929
|
+
const totalDebt = ca.debt + ca.accruedInterest + ca.accruedFees;
|
|
930
|
+
const r = ca.debt * baseRateWithFee / (totalDebt * RAY);
|
|
931
|
+
const caTokens = new AddressMap(ca.tokens.map((t) => [t.token, t]));
|
|
932
|
+
let qr = 0n;
|
|
933
|
+
for (const t of creditManager.collateralTokens) {
|
|
934
|
+
const b = caTokens.get(t);
|
|
935
|
+
if (b) {
|
|
936
|
+
qr += b.quota * BigInt(pool.pqk.quotas.get(t)?.rate ?? 0);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
qr = qr * (BigInt(feeInterest) + PERCENTAGE_FACTOR) / PERCENTAGE_FACTOR;
|
|
940
|
+
qr /= totalDebt;
|
|
941
|
+
return r + qr;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* {@inheritDoc ICreditAccountsService.getOptimalHFForPartialLiquidation}
|
|
945
|
+
**/
|
|
946
|
+
getOptimalHFForPartialLiquidation(ca) {
|
|
947
|
+
const borrowRate = this.getBorrowRate(ca);
|
|
948
|
+
return PERCENTAGE_FACTOR + (borrowRate < 100n ? borrowRate : 100n);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Internal wrapper for CreditAccountCompressor.getCreditAccounts + price updates wrapped into multicall
|
|
952
|
+
* @param args
|
|
953
|
+
* @param priceUpdateTxs
|
|
954
|
+
* @param blockNumber
|
|
955
|
+
* @returns
|
|
956
|
+
*/
|
|
957
|
+
async #getCreditAccounts(args, priceUpdateTxs, blockNumber) {
|
|
958
|
+
let resp;
|
|
959
|
+
if (priceUpdateTxs?.length) {
|
|
960
|
+
[resp] = await simulateWithPriceUpdates(this.client, {
|
|
961
|
+
priceUpdates: priceUpdateTxs,
|
|
962
|
+
contracts: [
|
|
963
|
+
{
|
|
964
|
+
abi: creditAccountCompressorAbi,
|
|
965
|
+
address: this.compressor,
|
|
966
|
+
functionName: "getCreditAccounts",
|
|
967
|
+
args
|
|
968
|
+
}
|
|
969
|
+
],
|
|
970
|
+
blockNumber,
|
|
971
|
+
gas: this.sdk.gasLimit
|
|
972
|
+
});
|
|
973
|
+
} else {
|
|
974
|
+
resp = await this.client.readContract({
|
|
975
|
+
abi: creditAccountCompressorAbi,
|
|
976
|
+
address: this.compressor,
|
|
977
|
+
functionName: "getCreditAccounts",
|
|
978
|
+
args,
|
|
979
|
+
blockNumber,
|
|
980
|
+
// @ts-expect-error
|
|
981
|
+
gas: this.sdk.gasLimit
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
this.logger?.debug(
|
|
985
|
+
{
|
|
986
|
+
accounts: resp[0]?.length ?? 0,
|
|
987
|
+
nextOffset: Number(resp[1])
|
|
988
|
+
},
|
|
989
|
+
"got credit accounts"
|
|
990
|
+
);
|
|
991
|
+
return resp;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
|
|
995
|
+
* Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
|
|
996
|
+
* Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
|
|
997
|
+
* @param amount - Number of vault shares (adapter tokens) to redeem
|
|
998
|
+
* @param creditManager - Credit manager address
|
|
999
|
+
* @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
|
|
1000
|
+
*/
|
|
1001
|
+
async getKYCUnwrapCalls(amount, creditManager) {
|
|
1002
|
+
const suite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
1003
|
+
const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
|
|
1004
|
+
if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
|
|
1005
|
+
return void 0;
|
|
1006
|
+
}
|
|
1007
|
+
const adapter = suite.creditManager.adapters.get(meta.addr);
|
|
1008
|
+
const adapterAddress = adapter?.address;
|
|
1009
|
+
if (!adapterAddress) {
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
const mc = [
|
|
1013
|
+
{
|
|
1014
|
+
target: adapterAddress,
|
|
1015
|
+
callData: encodeFunctionData({
|
|
1016
|
+
abi: ierc4626AdapterAbi,
|
|
1017
|
+
functionName: "redeem",
|
|
1018
|
+
args: [amount, ADDRESS_0X0, ADDRESS_0X0]
|
|
1019
|
+
})
|
|
1020
|
+
}
|
|
1021
|
+
];
|
|
1022
|
+
return mc;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
|
|
1026
|
+
* Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
|
|
1027
|
+
* Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
|
|
1028
|
+
* @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
|
|
1029
|
+
* @param creditManager - Credit manager address
|
|
1030
|
+
* @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
|
|
1031
|
+
*/
|
|
1032
|
+
async getKYCWrapCalls(amount, creditManager) {
|
|
1033
|
+
const suite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
1034
|
+
const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
|
|
1035
|
+
if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
|
|
1036
|
+
return void 0;
|
|
1037
|
+
}
|
|
1038
|
+
const adapter = suite.creditManager.adapters.get(meta.addr);
|
|
1039
|
+
const adapterAddress = adapter?.address;
|
|
1040
|
+
if (!adapterAddress) {
|
|
1041
|
+
return void 0;
|
|
1042
|
+
}
|
|
1043
|
+
const mc = [
|
|
1044
|
+
{
|
|
1045
|
+
target: adapterAddress,
|
|
1046
|
+
callData: encodeFunctionData({
|
|
1047
|
+
abi: ierc4626AdapterAbi,
|
|
1048
|
+
functionName: "deposit",
|
|
1049
|
+
args: [amount, ADDRESS_0X0]
|
|
1050
|
+
})
|
|
1051
|
+
}
|
|
1052
|
+
];
|
|
1053
|
+
return mc;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
|
|
1057
|
+
* Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
|
|
1058
|
+
* Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
|
|
1059
|
+
* @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
|
|
1060
|
+
* @param creditManager - Credit manager address
|
|
1061
|
+
* @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
|
|
1062
|
+
*/
|
|
1063
|
+
async getRedeemDiffCalls(amount, creditManager) {
|
|
1064
|
+
const suite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
1065
|
+
const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
|
|
1066
|
+
if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
|
|
1067
|
+
return void 0;
|
|
1068
|
+
}
|
|
1069
|
+
const adapter = suite.creditManager.adapters.get(meta.addr);
|
|
1070
|
+
const adapterAddress = adapter?.address;
|
|
1071
|
+
if (!adapterAddress) {
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
const mc = [
|
|
1075
|
+
{
|
|
1076
|
+
target: adapterAddress,
|
|
1077
|
+
callData: encodeFunctionData({
|
|
1078
|
+
abi: ierc4626AdapterAbi,
|
|
1079
|
+
functionName: "redeemDiff",
|
|
1080
|
+
args: [amount]
|
|
1081
|
+
})
|
|
1082
|
+
}
|
|
1083
|
+
];
|
|
1084
|
+
return mc;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Returns multicall entries to call depositDiff on the KYC ERC-4626 adapter for the given credit manager.
|
|
1088
|
+
* Deposits the leftover underlying (e.g. after decreasing debt) into the vault so the account does not hold excess underlying.
|
|
1089
|
+
* Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
|
|
1090
|
+
* @param amount - Leftover underlying amount to deposit into the vault (in underlying decimals)
|
|
1091
|
+
* @param creditManager - Credit manager address
|
|
1092
|
+
* @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
|
|
1093
|
+
*/
|
|
1094
|
+
async getDepositDiffCalls(amount, creditManager) {
|
|
1095
|
+
const suite = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
1096
|
+
const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
|
|
1097
|
+
if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
|
|
1098
|
+
return void 0;
|
|
1099
|
+
}
|
|
1100
|
+
const adapter = suite.creditManager.adapters.get(meta.addr);
|
|
1101
|
+
const adapterAddress = adapter?.address;
|
|
1102
|
+
if (!adapterAddress) {
|
|
1103
|
+
return void 0;
|
|
1104
|
+
}
|
|
1105
|
+
const mc = [
|
|
1106
|
+
{
|
|
1107
|
+
target: adapterAddress,
|
|
1108
|
+
callData: encodeFunctionData({
|
|
1109
|
+
abi: ierc4626AdapterAbi,
|
|
1110
|
+
functionName: "depositDiff",
|
|
1111
|
+
args: [amount]
|
|
1112
|
+
})
|
|
1113
|
+
}
|
|
1114
|
+
];
|
|
1115
|
+
return mc;
|
|
1116
|
+
}
|
|
6
1117
|
/**
|
|
7
1118
|
* {@inheritDoc ICreditAccountsService.setBot}
|
|
8
1119
|
*/
|
|
@@ -37,12 +1148,12 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
|
|
|
37
1148
|
args: [botAddress, permissions]
|
|
38
1149
|
})
|
|
39
1150
|
};
|
|
40
|
-
const calls = targetContract.type === "creditAccount" ? await this
|
|
1151
|
+
const calls = targetContract.type === "creditAccount" ? await this.#prependPriceUpdates(
|
|
41
1152
|
targetContract.creditManager,
|
|
42
1153
|
[addBotCall],
|
|
43
1154
|
targetContract
|
|
44
1155
|
) : [addBotCall];
|
|
45
|
-
const tx = targetContract.type === "creditAccount" ? await this
|
|
1156
|
+
const tx = targetContract.type === "creditAccount" ? await this.#multicallTx(cm, targetContract.creditAccount, calls) : void 0;
|
|
46
1157
|
return { tx, calls, creditFacade: cm.creditFacade };
|
|
47
1158
|
}
|
|
48
1159
|
/**
|
|
@@ -60,24 +1171,24 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
|
|
|
60
1171
|
);
|
|
61
1172
|
const operationCalls = [
|
|
62
1173
|
...assetsToWithdraw.map(
|
|
63
|
-
(a) => this
|
|
1174
|
+
(a) => this.#prepareWithdrawToken(
|
|
64
1175
|
creditAccount.creditFacade,
|
|
65
1176
|
a.token,
|
|
66
1177
|
a.balance,
|
|
67
1178
|
to
|
|
68
1179
|
)
|
|
69
1180
|
),
|
|
70
|
-
...this
|
|
1181
|
+
...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
|
|
71
1182
|
minQuota,
|
|
72
1183
|
averageQuota
|
|
73
1184
|
})
|
|
74
1185
|
];
|
|
75
|
-
const calls = await this
|
|
1186
|
+
const calls = await this.#prependPriceUpdates(
|
|
76
1187
|
creditAccount.creditManager,
|
|
77
1188
|
operationCalls,
|
|
78
1189
|
creditAccount
|
|
79
1190
|
);
|
|
80
|
-
const tx = await this
|
|
1191
|
+
const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
|
|
81
1192
|
return { tx, calls, creditFacade: cm.creditFacade };
|
|
82
1193
|
}
|
|
83
1194
|
/**
|
|
@@ -102,18 +1213,18 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
|
|
|
102
1213
|
creditAccount: ca
|
|
103
1214
|
});
|
|
104
1215
|
const operationCalls = [
|
|
105
|
-
...this
|
|
1216
|
+
...this.#prepareAddCollateral(ca.creditFacade, addCollateral, permits),
|
|
106
1217
|
...wrapCalls,
|
|
107
|
-
...this
|
|
108
|
-
...this
|
|
1218
|
+
...this.#prepareDisableQuotas(ca),
|
|
1219
|
+
...this.#prepareDecreaseDebt(ca),
|
|
109
1220
|
...unwrapCalls,
|
|
110
1221
|
...claimPath.calls,
|
|
111
1222
|
...assetsToWithdraw.map(
|
|
112
|
-
(t) => this
|
|
1223
|
+
(t) => this.#prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
|
|
113
1224
|
)
|
|
114
1225
|
];
|
|
115
|
-
const calls = operation === "close" ? operationCalls : await this
|
|
116
|
-
const tx = await this
|
|
1226
|
+
const calls = operation === "close" ? operationCalls : await this.#prependPriceUpdates(ca.creditManager, operationCalls, ca);
|
|
1227
|
+
const tx = await this.#closeCreditAccountTx(
|
|
117
1228
|
cm,
|
|
118
1229
|
ca.creditAccount,
|
|
119
1230
|
calls,
|
|
@@ -141,14 +1252,14 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
|
|
|
141
1252
|
const wrapCalls = await this.getDepositDiffCalls(1n, ca.creditManager) ?? [];
|
|
142
1253
|
const addCollateral = collateralAssets.filter((a) => a.balance > 0);
|
|
143
1254
|
const operationCalls = [
|
|
144
|
-
...this
|
|
1255
|
+
...this.#prepareAddCollateral(ca.creditFacade, addCollateral, permits),
|
|
145
1256
|
...claimPath.calls,
|
|
146
1257
|
...wrapCalls,
|
|
147
1258
|
...assetsToWithdraw.map(
|
|
148
|
-
(t) => this
|
|
1259
|
+
(t) => this.#prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
|
|
149
1260
|
)
|
|
150
1261
|
];
|
|
151
|
-
const calls = await this
|
|
1262
|
+
const calls = await this.#prependPriceUpdates(
|
|
152
1263
|
ca.creditManager,
|
|
153
1264
|
operationCalls,
|
|
154
1265
|
ca
|
|
@@ -184,17 +1295,349 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
|
|
|
184
1295
|
if (claimPath.calls.length === 0) throw new Error("No path to execute");
|
|
185
1296
|
const operationCalls = [
|
|
186
1297
|
...claimPath.calls,
|
|
187
|
-
...this
|
|
1298
|
+
...this.#prepareUpdateQuotas(ca.creditFacade, { minQuota, averageQuota })
|
|
188
1299
|
];
|
|
189
|
-
const calls = await this
|
|
1300
|
+
const calls = await this.#prependPriceUpdates(
|
|
190
1301
|
ca.creditManager,
|
|
191
1302
|
operationCalls,
|
|
192
1303
|
ca
|
|
193
1304
|
);
|
|
194
|
-
const tx = await this
|
|
1305
|
+
const tx = await this.#multicallTx(cm, ca.creditAccount, calls);
|
|
195
1306
|
return { tx, calls, creditFacade: cm.creditFacade };
|
|
196
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
|
|
1310
|
+
* {@inheritDoc ICreditAccountsService.getOnDemandPriceUpdates}
|
|
1311
|
+
**/
|
|
1312
|
+
async getOnDemandPriceUpdates(account, ignoreReservePrices) {
|
|
1313
|
+
const { creditManager, creditAccount } = account;
|
|
1314
|
+
const cm = this.sdk.marketRegister.findCreditManager(creditManager);
|
|
1315
|
+
const update = await this.#getUpdateForAccount(
|
|
1316
|
+
account,
|
|
1317
|
+
ignoreReservePrices
|
|
1318
|
+
);
|
|
1319
|
+
this.logger?.debug(
|
|
1320
|
+
{ account: creditAccount, manager: cm.name },
|
|
1321
|
+
`getting on demand price updates from ${update.txs.length} txs`
|
|
1322
|
+
);
|
|
1323
|
+
return getRawPriceUpdates(update);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Analyzes a multicall array and prepends necessary on-demand price feed updates.
|
|
1327
|
+
*
|
|
1328
|
+
* Deduplicates existing `onDemandPriceUpdates` calls
|
|
1329
|
+
*
|
|
1330
|
+
* @param creditManager - Address of the credit manager
|
|
1331
|
+
* @param calls - The multicall array to prepend price updates to
|
|
1332
|
+
* @param ca - Credit account slice, undefined when opening a new account
|
|
1333
|
+
* @param options - Optional settings for price update generation
|
|
1334
|
+
* @returns A new array with a single consolidated price update call prepended,
|
|
1335
|
+
* followed by the non-price-update calls in their original order
|
|
1336
|
+
*/
|
|
1337
|
+
async #prependPriceUpdates(creditManager, calls, ca, options) {
|
|
1338
|
+
const market = this.sdk.marketRegister.findByCreditManager(creditManager);
|
|
1339
|
+
const cm = this.sdk.marketRegister.findCreditManager(creditManager).creditManager;
|
|
1340
|
+
const { priceUpdates: existingUpdates, remainingCalls } = extractPriceUpdates(calls);
|
|
1341
|
+
const tokens = new AddressSet([
|
|
1342
|
+
cm.underlying,
|
|
1343
|
+
// underlying - always included
|
|
1344
|
+
...extractQuotaTokens(calls)
|
|
1345
|
+
// tokens from `updateQuota` calls
|
|
1346
|
+
]);
|
|
1347
|
+
if (ca) {
|
|
1348
|
+
for (const t of ca.tokens) {
|
|
1349
|
+
const isEnabled = (t.mask & ca.enabledTokensMask) !== 0n;
|
|
1350
|
+
if (t.balance > 10n && isEnabled) {
|
|
1351
|
+
tokens.add(t.token);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const ignoreReservePrices = options?.ignoreReservePrices;
|
|
1356
|
+
const priceFeeds = market.priceOracle.priceFeedsForTokens(Array.from(tokens), {
|
|
1357
|
+
main: true,
|
|
1358
|
+
reserve: !ignoreReservePrices
|
|
1359
|
+
});
|
|
1360
|
+
const tStr = tokens.map((t) => this.labelAddress(t)).join(", ");
|
|
1361
|
+
const remark = ignoreReservePrices ? " main" : "";
|
|
1362
|
+
this.logger?.debug(
|
|
1363
|
+
{ account: ca?.creditAccount, manager: cm.name },
|
|
1364
|
+
`prependPriceUpdates for ${tStr} from ${priceFeeds.length}${remark} price feeds`
|
|
1365
|
+
);
|
|
1366
|
+
const generatedUpdates = await this.sdk.priceFeeds.generatePriceFeedsUpdates(priceFeeds);
|
|
1367
|
+
const merged = mergePriceUpdates(existingUpdates, generatedUpdates);
|
|
1368
|
+
if (merged.length === 0) {
|
|
1369
|
+
return remainingCalls;
|
|
1370
|
+
}
|
|
1371
|
+
return [
|
|
1372
|
+
{
|
|
1373
|
+
target: cm.creditFacade,
|
|
1374
|
+
callData: encodeFunctionData({
|
|
1375
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1376
|
+
functionName: "onDemandPriceUpdates",
|
|
1377
|
+
args: [merged]
|
|
1378
|
+
})
|
|
1379
|
+
},
|
|
1380
|
+
...remainingCalls
|
|
1381
|
+
];
|
|
1382
|
+
}
|
|
1383
|
+
async #getUpdateForAccount(account, ignoreReservePrices) {
|
|
1384
|
+
const { creditManager, creditAccount, enabledTokensMask } = account;
|
|
1385
|
+
const market = this.sdk.marketRegister.findByCreditManager(creditManager);
|
|
1386
|
+
const cm = this.sdk.marketRegister.findCreditManager(creditManager).creditManager;
|
|
1387
|
+
const tokens = new AddressSet([cm.underlying]);
|
|
1388
|
+
for (const t of account.tokens) {
|
|
1389
|
+
const isEnabled = (t.mask & enabledTokensMask) !== 0n;
|
|
1390
|
+
if (t.balance > 10n && isEnabled) {
|
|
1391
|
+
tokens.add(t.token);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const priceFeeds = market.priceOracle.priceFeedsForTokens(Array.from(tokens), {
|
|
1395
|
+
main: true,
|
|
1396
|
+
reserve: !ignoreReservePrices
|
|
1397
|
+
});
|
|
1398
|
+
const tStr = tokens.map((t) => this.labelAddress(t)).join(", ");
|
|
1399
|
+
const remark = ignoreReservePrices ? " main" : "";
|
|
1400
|
+
this.logger?.debug(
|
|
1401
|
+
{ account: creditAccount, manager: cm.name },
|
|
1402
|
+
`generating price feed updates for ${tStr} from ${priceFeeds.length}${remark} price feeds`
|
|
1403
|
+
);
|
|
1404
|
+
return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(priceFeeds);
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* {@inheritDoc ICreditAccountsService.multicall}
|
|
1408
|
+
*/
|
|
1409
|
+
async multicall(creditAccount, calls, options) {
|
|
1410
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
1411
|
+
creditAccount.creditManager
|
|
1412
|
+
);
|
|
1413
|
+
const callsWithPrices = await this.#prependPriceUpdates(
|
|
1414
|
+
creditAccount.creditManager,
|
|
1415
|
+
calls,
|
|
1416
|
+
creditAccount,
|
|
1417
|
+
options
|
|
1418
|
+
);
|
|
1419
|
+
return cm.creditFacade.multicall(
|
|
1420
|
+
creditAccount.creditAccount,
|
|
1421
|
+
callsWithPrices
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* {@inheritDoc ICreditAccountsService.botMulticall}
|
|
1426
|
+
*/
|
|
1427
|
+
async botMulticall(creditAccount, calls, options) {
|
|
1428
|
+
const cm = this.sdk.marketRegister.findCreditManager(
|
|
1429
|
+
creditAccount.creditManager
|
|
1430
|
+
);
|
|
1431
|
+
const callsWithPrices = await this.#prependPriceUpdates(
|
|
1432
|
+
creditAccount.creditManager,
|
|
1433
|
+
calls,
|
|
1434
|
+
creditAccount,
|
|
1435
|
+
options
|
|
1436
|
+
);
|
|
1437
|
+
return cm.creditFacade.botMulticall(
|
|
1438
|
+
creditAccount.creditAccount,
|
|
1439
|
+
callsWithPrices
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
#prepareDisableQuotas(ca) {
|
|
1443
|
+
const calls = [];
|
|
1444
|
+
for (const { token, quota } of ca.tokens) {
|
|
1445
|
+
if (quota > 0n) {
|
|
1446
|
+
calls.push({
|
|
1447
|
+
target: ca.creditFacade,
|
|
1448
|
+
callData: encodeFunctionData({
|
|
1449
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1450
|
+
functionName: "updateQuota",
|
|
1451
|
+
args: [token, MIN_INT96, 0n]
|
|
1452
|
+
})
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return calls;
|
|
1457
|
+
}
|
|
1458
|
+
#prepareUpdateQuotas(creditFacade, { averageQuota, minQuota }) {
|
|
1459
|
+
const minRecord = assetsMap(minQuota);
|
|
1460
|
+
const calls = averageQuota.map((q) => {
|
|
1461
|
+
const minAsset = minRecord.get(q.token);
|
|
1462
|
+
const min = minAsset && minAsset?.balance > 0 ? minAsset.balance : 0n;
|
|
1463
|
+
return {
|
|
1464
|
+
target: creditFacade,
|
|
1465
|
+
callData: encodeFunctionData({
|
|
1466
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1467
|
+
functionName: "updateQuota",
|
|
1468
|
+
args: [q.token, q.balance, min]
|
|
1469
|
+
})
|
|
1470
|
+
};
|
|
1471
|
+
});
|
|
1472
|
+
return calls;
|
|
1473
|
+
}
|
|
1474
|
+
#prepareDecreaseDebt(ca) {
|
|
1475
|
+
if (ca.debt > 0n) {
|
|
1476
|
+
return [
|
|
1477
|
+
{
|
|
1478
|
+
target: ca.creditFacade,
|
|
1479
|
+
callData: encodeFunctionData({
|
|
1480
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1481
|
+
functionName: "decreaseDebt",
|
|
1482
|
+
args: [MAX_UINT256]
|
|
1483
|
+
})
|
|
1484
|
+
}
|
|
1485
|
+
];
|
|
1486
|
+
}
|
|
1487
|
+
return [];
|
|
1488
|
+
}
|
|
1489
|
+
#prepareWithdrawToken(creditFacade, token, amount, to) {
|
|
1490
|
+
return {
|
|
1491
|
+
target: creditFacade,
|
|
1492
|
+
callData: encodeFunctionData({
|
|
1493
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1494
|
+
functionName: "withdrawCollateral",
|
|
1495
|
+
args: [token, amount, to]
|
|
1496
|
+
})
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
#prepareIncreaseDebt(creditFacade, debt) {
|
|
1500
|
+
return {
|
|
1501
|
+
target: creditFacade,
|
|
1502
|
+
callData: encodeFunctionData({
|
|
1503
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1504
|
+
functionName: "increaseDebt",
|
|
1505
|
+
args: [debt]
|
|
1506
|
+
})
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
#prepareChangeDebt(creditFacade, change, isDecrease) {
|
|
1510
|
+
return {
|
|
1511
|
+
target: creditFacade,
|
|
1512
|
+
callData: encodeFunctionData({
|
|
1513
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1514
|
+
functionName: isDecrease ? "decreaseDebt" : "increaseDebt",
|
|
1515
|
+
args: [change]
|
|
1516
|
+
})
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
#prepareAddCollateral(creditFacade, assets, permits) {
|
|
1520
|
+
const calls = assets.map(({ token, balance }) => {
|
|
1521
|
+
const p = permits[token];
|
|
1522
|
+
if (p) {
|
|
1523
|
+
return {
|
|
1524
|
+
target: creditFacade,
|
|
1525
|
+
callData: encodeFunctionData({
|
|
1526
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1527
|
+
functionName: "addCollateralWithPermit",
|
|
1528
|
+
args: [token, balance, p.deadline, p.v, p.r, p.s]
|
|
1529
|
+
})
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
return {
|
|
1533
|
+
target: creditFacade,
|
|
1534
|
+
callData: encodeFunctionData({
|
|
1535
|
+
abi: iCreditFacadeMulticallV310Abi,
|
|
1536
|
+
functionName: "addCollateral",
|
|
1537
|
+
args: [token, balance]
|
|
1538
|
+
})
|
|
1539
|
+
};
|
|
1540
|
+
});
|
|
1541
|
+
return calls;
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Returns addresses of market configurators
|
|
1545
|
+
*/
|
|
1546
|
+
get marketConfigurators() {
|
|
1547
|
+
return this.sdk.marketRegister.marketConfigurators.map((mc) => mc.address);
|
|
1548
|
+
}
|
|
1549
|
+
get rewardCompressor() {
|
|
1550
|
+
return this.sdk.addressProvider.mustGetLatest(
|
|
1551
|
+
AP_REWARDS_COMPRESSOR,
|
|
1552
|
+
VERSION_RANGE_310
|
|
1553
|
+
)[0];
|
|
1554
|
+
}
|
|
1555
|
+
get peripheryCompressor() {
|
|
1556
|
+
return this.sdk.addressProvider.mustGetLatest(
|
|
1557
|
+
AP_PERIPHERY_COMPRESSOR,
|
|
1558
|
+
VERSION_RANGE_310
|
|
1559
|
+
)[0];
|
|
1560
|
+
}
|
|
1561
|
+
get compressor() {
|
|
1562
|
+
if (!this.#compressor) {
|
|
1563
|
+
[this.#compressor] = this.sdk.addressProvider.mustGetLatest(
|
|
1564
|
+
AP_CREDIT_ACCOUNT_COMPRESSOR,
|
|
1565
|
+
VERSION_RANGE_310
|
|
1566
|
+
);
|
|
1567
|
+
this.logger?.debug(
|
|
1568
|
+
`credit account compressor address: ${this.#compressor}`
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
return this.#compressor;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Wrapper that selects between credit facade and KYC factory
|
|
1575
|
+
* @param suite
|
|
1576
|
+
* @param to
|
|
1577
|
+
* @param calls
|
|
1578
|
+
* @param referralCode
|
|
1579
|
+
* @param kycOptions
|
|
1580
|
+
* @returns
|
|
1581
|
+
*/
|
|
1582
|
+
async #openCreditAccountTx(suite, to, calls, referralCode, kycOptions) {
|
|
1583
|
+
const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
|
|
1584
|
+
const factory = marketSuite.kycFactory;
|
|
1585
|
+
if (factory) {
|
|
1586
|
+
return factory.openCreditAccount(
|
|
1587
|
+
suite.creditManager.address,
|
|
1588
|
+
calls,
|
|
1589
|
+
kycOptions
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
return suite.creditFacade.openCreditAccount(to, calls, referralCode ?? 0n);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Wrapper that selects between credit facade and KYC factory
|
|
1596
|
+
* @param suite
|
|
1597
|
+
* @param creditAccount
|
|
1598
|
+
* @param calls
|
|
1599
|
+
* @param kycOptions
|
|
1600
|
+
* @returns
|
|
1601
|
+
*/
|
|
1602
|
+
async #multicallTx(suite, creditAccount, calls, kycOptions) {
|
|
1603
|
+
const marketSuite = this.sdk.marketRegister.findByCreditManager(
|
|
1604
|
+
suite.creditManager.address
|
|
1605
|
+
);
|
|
1606
|
+
const factory = marketSuite.kycFactory;
|
|
1607
|
+
if (factory) {
|
|
1608
|
+
return factory.multicall(creditAccount, calls, kycOptions);
|
|
1609
|
+
}
|
|
1610
|
+
return suite.creditFacade.multicall(creditAccount, calls);
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Wrapper that selects between credit facade and KYC factory
|
|
1614
|
+
* @param suite
|
|
1615
|
+
* @param creditAccount
|
|
1616
|
+
* @param calls
|
|
1617
|
+
* @param operation
|
|
1618
|
+
* @param kycOptions
|
|
1619
|
+
* @returns
|
|
1620
|
+
*/
|
|
1621
|
+
async #closeCreditAccountTx(suite, creditAccount, calls, operation, kycOptions) {
|
|
1622
|
+
const marketSuite = this.sdk.marketRegister.findByCreditManager(
|
|
1623
|
+
suite.creditManager.address
|
|
1624
|
+
);
|
|
1625
|
+
const factory = marketSuite.kycFactory;
|
|
1626
|
+
if (operation === "close") {
|
|
1627
|
+
if (factory) {
|
|
1628
|
+
throw new Error(
|
|
1629
|
+
"CloseOptions=close is not supported for KYC underlying credit accounts"
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
return suite.creditFacade.closeCreditAccount(creditAccount, calls);
|
|
1633
|
+
}
|
|
1634
|
+
if (factory) {
|
|
1635
|
+
return factory.multicall(creditAccount, calls, kycOptions);
|
|
1636
|
+
}
|
|
1637
|
+
return suite.creditFacade.multicall(creditAccount, calls);
|
|
1638
|
+
}
|
|
197
1639
|
}
|
|
198
1640
|
export {
|
|
199
|
-
|
|
1641
|
+
CreditAccountsServiceV310,
|
|
1642
|
+
getWithdrawalCompressorAddress
|
|
200
1643
|
};
|