@curvefi/llamalend-api 1.0.41 → 1.1.0

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.
@@ -0,0 +1,991 @@
1
+ import memoize from "memoizee";
2
+ import type { TAmount, TGas, IDict, IQuote, ILeverageMetrics, GetExpectedFn } from "../../interfaces.js";
3
+ import type { LendMarketTemplate } from "../LendMarketTemplate.js";
4
+ import {
5
+ _getAddress,
6
+ parseUnits,
7
+ BN,
8
+ toBN,
9
+ fromBN,
10
+ ensureAllowance,
11
+ hasAllowance,
12
+ ensureAllowanceEstimateGas,
13
+ formatUnits,
14
+ smartNumber,
15
+ formatNumber,
16
+ _mulBy1_3,
17
+ DIGas,
18
+ buildCalldataForLeverageZapV2,
19
+ } from "../../utils.js";
20
+ import {Llamalend} from "../../llamalend.js";
21
+ import BigNumber from "bignumber.js";
22
+
23
+ /**
24
+ * LeverageZapV2 module for LendMarketTemplate
25
+ */
26
+ export class LeverageZapV2Module {
27
+ private market: LendMarketTemplate;
28
+ private llamalend: Llamalend;
29
+
30
+ constructor(market: LendMarketTemplate) {
31
+ this.market = market;
32
+ this.llamalend = market.getLlamalend();
33
+ }
34
+
35
+ private _getMarketId = (): number => Number(this.market.id.split("-").slice(-1)[0]);
36
+
37
+ // ============ CREATE LOAN METHODS ============
38
+
39
+ public hasLeverage = (): boolean => {
40
+ return this.llamalend.constants.ALIASES.leverage_zap_v2 !== this.llamalend.constants.ZERO_ADDRESS &&
41
+ this._getMarketId() >= Number(this.llamalend.constants.ALIASES["leverage_markets_start_id"]);
42
+ }
43
+
44
+ public _checkLeverageZap(): void {
45
+ if (!this.hasLeverage()) {
46
+ throw Error("This market does not support leverage");
47
+ }
48
+ }
49
+
50
+ public async _get_k_effective_BN(N: number): Promise<BigNumber> {
51
+ // d_k_effective: uint256 = (1 - loan_discount) * sqrt((A-1)/A) / N
52
+ // k_effective = d_k_effective * sum_{0..N-1}(((A-1) / A)**k)
53
+ const { loan_discount, A } = await this.market.stats.parameters();
54
+ const A_BN = BN(A);
55
+ const A_ratio_BN = A_BN.minus(1).div(A_BN);
56
+
57
+ const d_k_effective_BN = BN(100).minus(loan_discount).div(100).times(A_ratio_BN.sqrt()).div(N);
58
+ let S = BN(0);
59
+ for (let n = 0; n < N; n++) {
60
+ S = S.plus(A_ratio_BN.pow(n))
61
+ }
62
+
63
+ return d_k_effective_BN.times(S);
64
+ }
65
+
66
+ public async maxLeverage(N: number): Promise<string> {
67
+ // max_leverage = 1 / (k_effective - 1)
68
+ const k_effective_BN = await this._get_k_effective_BN(N);
69
+
70
+ return BN(1).div(BN(1).minus(k_effective_BN)).toString()
71
+ }
72
+
73
+ public async leverageCreateLoanMaxRecv({ userCollateral, userBorrowed, range, getExpected }: {
74
+ userCollateral: TAmount,
75
+ userBorrowed: TAmount,
76
+ range: number,
77
+ getExpected: GetExpectedFn
78
+ }): Promise<{
79
+ maxDebt: string,
80
+ maxTotalCollateral: string,
81
+ userCollateral: string,
82
+ collateralFromUserBorrowed: string,
83
+ collateralFromMaxDebt: string,
84
+ maxLeverage: string,
85
+ avgPrice: string,
86
+ }> {
87
+ // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg)
88
+ this._checkLeverageZap();
89
+ if (range > 0) this.market._checkRange(range);
90
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
91
+ const _userBorrowed = parseUnits(userBorrowed, this.market.borrowed_token.decimals);
92
+
93
+ const oraclePriceBand = await this.market.oraclePriceBand();
94
+ let pAvgBN = BN(await this.market.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
95
+ let maxBorrowablePrevBN = BN(0);
96
+ let maxBorrowableBN = BN(0);
97
+ let _userEffectiveCollateral = BigInt(0);
98
+ let _maxLeverageCollateral = BigInt(0);
99
+
100
+ const contract = this.llamalend.contracts[this.llamalend.constants.ALIASES.leverage_zap_v2].contract;
101
+ for (let i = 0; i < 5; i++) {
102
+ maxBorrowablePrevBN = maxBorrowableBN;
103
+ _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.market.collateral_token.decimals);
104
+ let _maxBorrowable = await contract.max_borrowable(this.market.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, range, fromBN(pAvgBN));
105
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000)
106
+ if (_maxBorrowable === BigInt(0)) break;
107
+ maxBorrowableBN = toBN(_maxBorrowable, this.market.borrowed_token.decimals);
108
+
109
+ if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) {
110
+ maxBorrowableBN = maxBorrowablePrevBN;
111
+ break;
112
+ }
113
+
114
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
115
+ const _maxAdditionalCollateral = BigInt((await getExpected(
116
+ this.market.addresses.borrowed_token,
117
+ this.market.addresses.collateral_token,
118
+ _maxBorrowable + _userBorrowed,
119
+ this.market.addresses.amm
120
+ )).outAmount);
121
+
122
+ pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.market.collateral_token.decimals));
123
+ _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.market.collateral_token.decimals);
124
+ }
125
+
126
+ const userEffectiveCollateralBN = maxBorrowableBN.gt(0) ? toBN(_userEffectiveCollateral, this.market.collateral_token.decimals) : BN(0);
127
+ const maxLeverageCollateralBN = toBN(_maxLeverageCollateral, this.market.collateral_token.decimals);
128
+
129
+ return {
130
+ maxDebt: formatNumber(maxBorrowableBN.toString(), this.market.borrowed_token.decimals),
131
+ maxTotalCollateral: formatNumber(maxLeverageCollateralBN.plus(userEffectiveCollateralBN).toString(), this.market.collateral_token.decimals),
132
+ userCollateral: formatNumber(userCollateral, this.market.collateral_token.decimals),
133
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.market.collateral_token.decimals),
134
+ collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN.toString(), this.market.collateral_token.decimals),
135
+ maxLeverage: maxLeverageCollateralBN.plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(),
136
+ avgPrice: pAvgBN.toString(),
137
+ };
138
+ }
139
+
140
+ public leverageCreateLoanMaxRecvAllRanges = memoize(async ({ userCollateral, userBorrowed, getExpected }: {
141
+ userCollateral: TAmount,
142
+ userBorrowed: TAmount,
143
+ getExpected: GetExpectedFn
144
+ }): Promise<IDict<{
145
+ maxDebt: string,
146
+ maxTotalCollateral: string,
147
+ userCollateral: string,
148
+ collateralFromUserBorrowed: string,
149
+ collateralFromMaxDebt: string,
150
+ maxLeverage: string,
151
+ avgPrice: string,
152
+ }>> => {
153
+ this._checkLeverageZap();
154
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
155
+ const contract = this.llamalend.contracts[this.llamalend.constants.ALIASES.leverage_zap_v2].multicallContract;
156
+
157
+ const oraclePriceBand = await this.market.oraclePriceBand();
158
+ const pAvgApproxBN = BN(await this.market.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
159
+ let pAvgBN: BigNumber | null = null;
160
+ const arrLength = this.market.maxBands - this.market.minBands + 1;
161
+ let maxLeverageCollateralBN: BigNumber[] = new Array(arrLength).fill(BN(0));
162
+ let _maxLeverageCollateral: bigint[] = new Array(arrLength).fill(BigInt(0));
163
+ let maxBorrowablePrevBN: BigNumber[] = new Array(arrLength).fill(BN(0));
164
+ let maxBorrowableBN: BigNumber[] = new Array(arrLength).fill(BN(0));
165
+ let _maxBorrowable: bigint[] = new Array(arrLength).fill(BigInt(0));
166
+
167
+ for (let i = 0; i < 5; i++) {
168
+ const pBN = pAvgBN ?? pAvgApproxBN;
169
+ maxBorrowablePrevBN = maxBorrowableBN;
170
+ const _userEffectiveCollateral: bigint = _userCollateral + fromBN(BN(userBorrowed).div(pBN), this.market.collateral_token.decimals);
171
+ const calls = [];
172
+ for (let N = this.market.minBands; N <= this.market.maxBands; N++) {
173
+ const j = N - this.market.minBands;
174
+ calls.push(contract.max_borrowable(this.market.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral[j], N, fromBN(pBN)));
175
+ }
176
+ _maxBorrowable = (await this.llamalend.multicallProvider.all(calls) as bigint[]).map((_mb) => _mb * BigInt(998) / BigInt(1000));
177
+ maxBorrowableBN = _maxBorrowable.map((_mb) => toBN(_mb, this.market.borrowed_token.decimals));
178
+
179
+ const deltaBN = maxBorrowableBN.map((mb, l) => mb.minus(maxBorrowablePrevBN[l]).abs().div(mb));
180
+ if (BigNumber.max(...deltaBN).lt(0.0005)) {
181
+ maxBorrowableBN = maxBorrowablePrevBN;
182
+ break;
183
+ }
184
+
185
+ if (pAvgBN === null){
186
+ const _y = BigInt((await getExpected(
187
+ this.market.addresses.borrowed_token,
188
+ this.market.addresses.collateral_token,
189
+ _maxBorrowable[0],
190
+ this.market.addresses.amm
191
+ )).outAmount);
192
+ const yBN = toBN(_y, this.market.collateral_token.decimals);
193
+ pAvgBN = maxBorrowableBN[0].div(yBN);
194
+ }
195
+
196
+ maxLeverageCollateralBN = maxBorrowableBN.map((mb) => mb.div(pAvgBN as BigNumber));
197
+ _maxLeverageCollateral = maxLeverageCollateralBN.map((mlc) => fromBN(mlc, this.market.collateral_token.decimals));
198
+ }
199
+
200
+ const userEffectiveCollateralBN = BN(userCollateral).plus(BN(userBorrowed).div(pAvgBN as BigNumber));
201
+
202
+ const res: IDict<{
203
+ maxDebt: string,
204
+ maxTotalCollateral: string,
205
+ userCollateral: string,
206
+ collateralFromUserBorrowed: string,
207
+ collateralFromMaxDebt: string,
208
+ maxLeverage: string,
209
+ avgPrice: string,
210
+ }> = {};
211
+ for (let N = this.market.minBands; N <= this.market.maxBands; N++) {
212
+ const j = N - this.market.minBands;
213
+ res[N] = {
214
+ maxDebt: formatNumber(maxBorrowableBN[j].toString(), this.market.borrowed_token.decimals),
215
+ maxTotalCollateral: formatNumber(maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).toString(), this.market.collateral_token.decimals),
216
+ userCollateral: formatNumber(userCollateral, this.market.collateral_token.decimals),
217
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN as BigNumber).toString(), this.market.collateral_token.decimals),
218
+ collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN[j].toString(), this.market.collateral_token.decimals),
219
+ maxLeverage: maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(),
220
+ avgPrice: (pAvgBN as BigNumber).toString(),
221
+ };
222
+ }
223
+
224
+ return res;
225
+ },
226
+ {
227
+ promise: true,
228
+ maxAge: 60 * 1000, // 1m
229
+ });
230
+
231
+ private _leverageExpectedCollateral = async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, quote: IQuote, user?: string):
232
+ Promise<{ _futureStateCollateral: bigint, _totalCollateral: bigint, _userCollateral: bigint,
233
+ _collateralFromUserBorrowed: bigint, _collateralFromDebt: bigint, avgPrice: string }> => {
234
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
235
+ const _debt = parseUnits(debt, this.market.borrowed_token.decimals);
236
+ const _userBorrowed = parseUnits(userBorrowed, this.market.borrowed_token.decimals);
237
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
238
+ const _additionalCollateral = BigInt(quote.outAmount);
239
+ const _collateralFromDebt = _debt * BigInt(10**18) / (_debt + _userBorrowed) * _additionalCollateral / BigInt(10**18);
240
+ const _collateralFromUserBorrowed = _additionalCollateral - _collateralFromDebt;
241
+ let _stateCollateral = BigInt(0);
242
+ if (user) {
243
+ const { _collateral, _borrowed } = await this.market._userState(user);
244
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
245
+ _stateCollateral = _collateral;
246
+ }
247
+ const _totalCollateral = _userCollateral + _additionalCollateral;
248
+ const _futureStateCollateral = _stateCollateral + _totalCollateral;
249
+ const avgPrice = toBN(_debt + _userBorrowed, this.market.borrowed_token.decimals).div(toBN(_additionalCollateral, this.market.collateral_token.decimals)).toString();
250
+
251
+ return { _futureStateCollateral, _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice };
252
+ };
253
+
254
+ public async leverageCreateLoanExpectedCollateral({ userCollateral, userBorrowed, debt, quote }: {
255
+ userCollateral: TAmount,
256
+ userBorrowed: TAmount,
257
+ debt: TAmount,
258
+ quote: IQuote
259
+ }): Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, leverage: string, avgPrice: string }> {
260
+ this._checkLeverageZap();
261
+
262
+ const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } =
263
+ await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt, quote);
264
+ return {
265
+ totalCollateral: formatUnits(_totalCollateral, this.market.collateral_token.decimals),
266
+ userCollateral: formatUnits(_userCollateral, this.market.collateral_token.decimals),
267
+ collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.market.collateral_token.decimals),
268
+ collateralFromDebt: formatUnits(_collateralFromDebt, this.market.collateral_token.decimals),
269
+ leverage: toBN(_collateralFromDebt + _userCollateral + _collateralFromUserBorrowed, this.market.collateral_token.decimals)
270
+ .div(toBN(_userCollateral + _collateralFromUserBorrowed, this.market.collateral_token.decimals)).toString(),
271
+ avgPrice,
272
+ }
273
+ }
274
+
275
+ public async leverageCreateLoanExpectedMetrics({ userCollateral, userBorrowed, debt, range, quote, healthIsFull = true }: {
276
+ userCollateral: TAmount,
277
+ userBorrowed: TAmount,
278
+ debt: TAmount,
279
+ range: number,
280
+ quote: IQuote,
281
+ healthIsFull?: boolean
282
+ }): Promise<ILeverageMetrics> {
283
+ this._checkLeverageZap();
284
+
285
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, range, quote);
286
+
287
+ const prices = await this.market._getPrices(_n2, _n1);
288
+ const health = await this._leverageHealth(userCollateral, userBorrowed, debt, range, quote, healthIsFull);
289
+
290
+ return {
291
+ priceImpact: quote.priceImpact,
292
+ bands: [Number(_n2), Number(_n1)],
293
+ prices,
294
+ health,
295
+ }
296
+ }
297
+
298
+ public async leverageCreateLoanMaxRange({ userCollateral, userBorrowed, debt, getExpected }: {
299
+ userCollateral: TAmount,
300
+ userBorrowed: TAmount,
301
+ debt: TAmount,
302
+ getExpected: GetExpectedFn
303
+ }): Promise<number> {
304
+ this._checkLeverageZap();
305
+ const maxRecv = await this.leverageCreateLoanMaxRecvAllRanges({ userCollateral, userBorrowed, getExpected });
306
+ for (let N = this.market.minBands; N <= this.market.maxBands; N++) {
307
+ if (BN(debt).gt(maxRecv[N].maxDebt)) return N - 1;
308
+ }
309
+
310
+ return this.market.maxBands;
311
+ }
312
+
313
+ private _leverageCalcN1 = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, quote: IQuote, user?: string): Promise<bigint> => {
314
+ if (range > 0) this.market._checkRange(range);
315
+ let _stateDebt = BigInt(0);
316
+ if (user) {
317
+ const { _debt, _borrowed, _N } = await this.market._userState(user);
318
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
319
+ _stateDebt = _debt;
320
+ if (range < 0) range = Number(this.llamalend.formatUnits(_N, 0));
321
+ }
322
+ const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt, quote, user);
323
+ const _debt = _stateDebt + parseUnits(debt, this.market.borrowed_token.decimals);
324
+ return await this.llamalend.contracts[this.market.addresses.controller].contract.calculate_debt_n1(_futureStateCollateral, _debt, range, this.llamalend.constantOptions);
325
+ },
326
+ {
327
+ promise: true,
328
+ maxAge: 60 * 1000, // 1m
329
+ });
330
+
331
+ private _leverageCalcN1AllRanges = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, maxN: number, quote: IQuote): Promise<bigint[]> => {
332
+ const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt, quote);
333
+ const _debt = parseUnits(debt, this.market.borrowed_token.decimals);
334
+ const calls = [];
335
+ for (let N = this.market.minBands; N <= maxN; N++) {
336
+ calls.push(this.llamalend.contracts[this.market.addresses.controller].multicallContract.calculate_debt_n1(_futureStateCollateral, _debt, N));
337
+ }
338
+ return await this.llamalend.multicallProvider.all(calls) as bigint[];
339
+ },
340
+ {
341
+ promise: true,
342
+ maxAge: 60 * 1000, // 1m
343
+ });
344
+
345
+ private async _leverageBands(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, quote: IQuote, user?: string): Promise<[bigint, bigint]> {
346
+ const _n1 = await this._leverageCalcN1(userCollateral, userBorrowed, debt, range, quote, user);
347
+ if (range < 0) {
348
+ const { N } = await this.market.userState(user);
349
+ range = Number(N);
350
+ }
351
+ const _n2 = _n1 + BigInt(range - 1);
352
+
353
+ return [_n2, _n1];
354
+ }
355
+
356
+ private async _leverageCreateLoanBandsAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, getExpected: GetExpectedFn, quote: IQuote): Promise<IDict<[bigint, bigint]>> {
357
+ const maxN = await this.leverageCreateLoanMaxRange({ userCollateral, userBorrowed, debt, getExpected });
358
+ const _n1_arr = await this._leverageCalcN1AllRanges(userCollateral, userBorrowed, debt, maxN, quote);
359
+ const _n2_arr: bigint[] = [];
360
+ for (let N = this.market.minBands; N <= maxN; N++) {
361
+ _n2_arr.push(_n1_arr[N - this.market.minBands] + BigInt(N - 1));
362
+ }
363
+
364
+ const _bands: IDict<[bigint, bigint]> = {};
365
+ for (let N = this.market.minBands; N <= maxN; N++) {
366
+ _bands[N] = [_n2_arr[N - this.market.minBands], _n1_arr[N - this.market.minBands]];
367
+ }
368
+
369
+ return _bands;
370
+ }
371
+
372
+ public async leverageCreateLoanBandsAllRanges({ userCollateral, userBorrowed, debt, getExpected, quote }: {
373
+ userCollateral: TAmount,
374
+ userBorrowed: TAmount,
375
+ debt: TAmount,
376
+ getExpected: GetExpectedFn,
377
+ quote: IQuote
378
+ }): Promise<IDict<[number, number] | null>> {
379
+ this._checkLeverageZap();
380
+ const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt, getExpected, quote);
381
+
382
+ const bands: { [index: number]: [number, number] | null } = {};
383
+ for (let N = this.market.minBands; N <= this.market.maxBands; N++) {
384
+ if (_bands[N]) {
385
+ bands[N] = _bands[N].map(Number) as [number, number];
386
+ } else {
387
+ bands[N] = null
388
+ }
389
+ }
390
+
391
+ return bands;
392
+ }
393
+
394
+ public async leverageCreateLoanPricesAllRanges({ userCollateral, userBorrowed, debt, getExpected, quote }: {
395
+ userCollateral: TAmount,
396
+ userBorrowed: TAmount,
397
+ debt: TAmount,
398
+ getExpected: GetExpectedFn,
399
+ quote: IQuote
400
+ }): Promise<IDict<[string, string] | null>> {
401
+ this._checkLeverageZap();
402
+ const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt, getExpected, quote);
403
+
404
+ const prices: { [index: number]: [string, string] | null } = {};
405
+ for (let N = this.market.minBands; N <= this.market.maxBands; N++) {
406
+ if (_bands[N]) {
407
+ prices[N] = await this.market._calcPrices(..._bands[N]);
408
+ } else {
409
+ prices[N] = null
410
+ }
411
+ }
412
+
413
+ return prices;
414
+ }
415
+
416
+ private async _leverageHealth(
417
+ userCollateral: TAmount,
418
+ userBorrowed: TAmount,
419
+ dDebt: TAmount,
420
+ range: number,
421
+ quote: IQuote,
422
+ full: boolean,
423
+ user = this.llamalend.constants.ZERO_ADDRESS
424
+ ): Promise<string> {
425
+ if (range > 0) this.market._checkRange(range);
426
+ const { _totalCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, quote, user);
427
+ const { _borrowed, _N } = await this.market._userState(user);
428
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
429
+ if (range < 0) range = Number(this.llamalend.formatUnits(_N, 0));
430
+ const _dDebt = parseUnits(dDebt, this.market.borrowed_token.decimals);
431
+
432
+ const contract = this.llamalend.contracts[this.market.addresses.controller].contract;
433
+ let _health = await contract.health_calculator(user, _totalCollateral, _dDebt, full, range, this.llamalend.constantOptions) as bigint;
434
+ _health = _health * BigInt(100);
435
+
436
+ return formatUnits(_health);
437
+ }
438
+
439
+ public async leverageCreateLoanIsApproved({ userCollateral, userBorrowed }: {
440
+ userCollateral: TAmount,
441
+ userBorrowed: TAmount
442
+ }): Promise<boolean> {
443
+ this._checkLeverageZap();
444
+ const collateralAllowance = await hasAllowance.call(this.llamalend,
445
+ [this.market.collateral_token.address], [userCollateral], this.llamalend.signerAddress, this.market.addresses.controller);
446
+ const borrowedAllowance = await hasAllowance.call(this.llamalend,
447
+ [this.market.borrowed_token.address], [userBorrowed], this.llamalend.signerAddress, this.llamalend.constants.ALIASES.leverage_zap_v2);
448
+
449
+ return collateralAllowance && borrowedAllowance
450
+ }
451
+
452
+ public async leverageCreateLoanApproveEstimateGas ({ userCollateral, userBorrowed }: {
453
+ userCollateral: TAmount,
454
+ userBorrowed: TAmount
455
+ }): Promise<TGas> {
456
+ this._checkLeverageZap();
457
+ const collateralGas = await ensureAllowanceEstimateGas.call(this.llamalend,
458
+ [this.market.collateral_token.address], [userCollateral], this.market.addresses.controller);
459
+ const borrowedGas = await ensureAllowanceEstimateGas.call(this.llamalend,
460
+ [this.market.borrowed_token.address], [userBorrowed], this.llamalend.constants.ALIASES.leverage_zap_v2);
461
+
462
+ if(Array.isArray(collateralGas) && Array.isArray(borrowedGas)) {
463
+ return [collateralGas[0] + borrowedGas[0], collateralGas[1] + borrowedGas[1]]
464
+ } else {
465
+ return (collateralGas as number) + (borrowedGas as number)
466
+ }
467
+ }
468
+
469
+ public async leverageCreateLoanApprove({ userCollateral, userBorrowed }: {
470
+ userCollateral: TAmount,
471
+ userBorrowed: TAmount
472
+ }): Promise<string[]> {
473
+ this._checkLeverageZap();
474
+ const collateralApproveTx = await ensureAllowance.call(this.llamalend,
475
+ [this.market.collateral_token.address], [userCollateral], this.market.addresses.controller);
476
+ const borrowedApproveTx = await ensureAllowance.call(this.llamalend,
477
+ [this.market.borrowed_token.address], [userBorrowed], this.llamalend.constants.ALIASES.leverage_zap_v2);
478
+
479
+ return [...collateralApproveTx, ...borrowedApproveTx]
480
+ }
481
+
482
+ private async _leverageCreateLoan(
483
+ userCollateral: TAmount,
484
+ userBorrowed: TAmount,
485
+ debt: TAmount,
486
+ range: number,
487
+ router: string,
488
+ calldata: string,
489
+ estimateGas: boolean
490
+ ): Promise<string | TGas> {
491
+ if (await this.market.userLoanExists()) throw Error("Loan already created");
492
+ this.market._checkRange(range);
493
+
494
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
495
+ const _userBorrowed = parseUnits(userBorrowed, this.market.borrowed_token.decimals);
496
+ const _debt = parseUnits(debt, this.market.borrowed_token.decimals);
497
+
498
+
499
+ const zapCalldata = buildCalldataForLeverageZapV2(router, calldata)
500
+ const contract = this.llamalend.contracts[this.market.addresses.controller].contract;
501
+ const gas = await contract.create_loan_extended.estimateGas(
502
+ _userCollateral,
503
+ _debt,
504
+ range,
505
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
506
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
507
+ zapCalldata,
508
+ { ...this.llamalend.constantOptions }
509
+ );
510
+ if (estimateGas) return smartNumber(gas);
511
+
512
+ await this.llamalend.updateFeeData();
513
+ const gasLimit = _mulBy1_3(DIGas(gas));
514
+ return (await contract.create_loan_extended(
515
+ _userCollateral,
516
+ _debt,
517
+ range,
518
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
519
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
520
+ zapCalldata,
521
+ { ...this.llamalend.options, gasLimit }
522
+ )).hash
523
+ }
524
+
525
+ public async leverageCreateLoanEstimateGas({ userCollateral, userBorrowed, debt, range, router, calldata }: {
526
+ userCollateral: TAmount,
527
+ userBorrowed: TAmount,
528
+ debt: TAmount,
529
+ range: number,
530
+ router: string,
531
+ calldata: string
532
+ }): Promise<number> {
533
+ this._checkLeverageZap();
534
+ if (!(await this.leverageCreateLoanIsApproved({ userCollateral, userBorrowed }))) throw Error("Approval is needed for gas estimation");
535
+ return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, router, calldata, true) as number;
536
+ }
537
+
538
+ public async leverageCreateLoan({ userCollateral, userBorrowed, debt, range, router, calldata }: {
539
+ userCollateral: TAmount,
540
+ userBorrowed: TAmount,
541
+ debt: TAmount,
542
+ range: number,
543
+ router: string,
544
+ calldata: string
545
+ }): Promise<string> {
546
+ this._checkLeverageZap();
547
+ await this.leverageCreateLoanApprove({ userCollateral, userBorrowed });
548
+ return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, router, calldata, false) as string;
549
+ }
550
+
551
+ // ---------------- LEVERAGE BORROW MORE ----------------
552
+
553
+ public async leverageBorrowMoreMaxRecv({ userCollateral, userBorrowed, getExpected, address = "" }: {
554
+ userCollateral: TAmount,
555
+ userBorrowed: TAmount,
556
+ getExpected: GetExpectedFn,
557
+ address?: string
558
+ }): Promise<{
559
+ maxDebt: string,
560
+ maxTotalCollateral: string,
561
+ userCollateral: string,
562
+ collateralFromUserBorrowed: string,
563
+ collateralFromMaxDebt: string,
564
+ avgPrice: string,
565
+ }> {
566
+ // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg)
567
+ this._checkLeverageZap();
568
+ address = _getAddress.call(this.llamalend, address);
569
+ const { _collateral: _stateCollateral, _borrowed: _stateBorrowed, _debt: _stateDebt, _N } = await this.market._userState(address);
570
+ if (_stateBorrowed > BigInt(0)) throw Error(`User ${address} is already in liquidation mode`);
571
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
572
+ const controllerContract = this.llamalend.contracts[this.market.addresses.controller].contract;
573
+ const _borrowedFromStateCollateral = await controllerContract.max_borrowable(_stateCollateral, _N, _stateDebt, this.llamalend.constantOptions) - _stateDebt;
574
+ const _userBorrowed = _borrowedFromStateCollateral + parseUnits(userBorrowed, this.market.borrowed_token.decimals);
575
+ userBorrowed = formatUnits(_userBorrowed, this.market.borrowed_token.decimals);
576
+
577
+ const oraclePriceBand = await this.market.oraclePriceBand();
578
+ let pAvgBN = BN(await this.market.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
579
+ let maxBorrowablePrevBN = BN(0);
580
+ let maxBorrowableBN = BN(0);
581
+ let _userEffectiveCollateral = BigInt(0);
582
+ let _maxLeverageCollateral = BigInt(0);
583
+
584
+ const contract = this.llamalend.contracts[this.llamalend.constants.ALIASES.leverage_zap_v2].contract;
585
+ for (let i = 0; i < 5; i++) {
586
+ maxBorrowablePrevBN = maxBorrowableBN;
587
+ _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.market.collateral_token.decimals);
588
+ let _maxBorrowable = await contract.max_borrowable(this.market.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, _N, fromBN(pAvgBN));
589
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000);
590
+ if (_maxBorrowable === BigInt(0)) break;
591
+ maxBorrowableBN = toBN(_maxBorrowable, this.market.borrowed_token.decimals);
592
+
593
+ if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) {
594
+ maxBorrowableBN = maxBorrowablePrevBN;
595
+ break;
596
+ }
597
+
598
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
599
+ const _maxAdditionalCollateral = BigInt((await getExpected(
600
+ this.market.addresses.borrowed_token, this.market.addresses.collateral_token, _maxBorrowable + _userBorrowed, this.market.addresses.amm)).outAmount);
601
+ pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.market.collateral_token.decimals));
602
+ _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.market.collateral_token.decimals);
603
+ }
604
+
605
+ if (maxBorrowableBN.eq(0)) _userEffectiveCollateral = BigInt(0);
606
+ const _maxTotalCollateral = _userEffectiveCollateral + _maxLeverageCollateral
607
+ let _maxBorrowable = await controllerContract.max_borrowable(_stateCollateral + _maxTotalCollateral, _N, _stateDebt, this.llamalend.constantOptions) - _stateDebt;
608
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000);
609
+
610
+ return {
611
+ maxDebt: formatUnits(_maxBorrowable, this.market.borrowed_token.decimals),
612
+ maxTotalCollateral: formatUnits(_maxTotalCollateral, this.market.collateral_token.decimals),
613
+ userCollateral: formatNumber(userCollateral, this.market.collateral_token.decimals),
614
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.market.collateral_token.decimals),
615
+ collateralFromMaxDebt: formatUnits(_maxLeverageCollateral, this.market.collateral_token.decimals),
616
+ avgPrice: pAvgBN.toString(),
617
+ };
618
+ }
619
+
620
+ public async leverageBorrowMoreExpectedCollateral({ userCollateral, userBorrowed, dDebt, quote, address = "" }: {
621
+ userCollateral: TAmount,
622
+ userBorrowed: TAmount,
623
+ dDebt: TAmount,
624
+ quote: IQuote,
625
+ address?: string
626
+ }): Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, avgPrice: string }> {
627
+ this._checkLeverageZap();
628
+ address = _getAddress.call(this.llamalend, address);
629
+
630
+ const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } =
631
+ await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, quote, address);
632
+ return {
633
+ totalCollateral: formatUnits(_totalCollateral, this.market.collateral_token.decimals),
634
+ userCollateral: formatUnits(_userCollateral, this.market.collateral_token.decimals),
635
+ collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.market.collateral_token.decimals),
636
+ collateralFromDebt: formatUnits(_collateralFromDebt, this.market.collateral_token.decimals),
637
+ avgPrice,
638
+ }
639
+ }
640
+
641
+ public async leverageBorrowMoreExpectedMetrics({ userCollateral, userBorrowed, debt, quote, healthIsFull = true, address = "" }: {
642
+ userCollateral: TAmount,
643
+ userBorrowed: TAmount,
644
+ debt: TAmount,
645
+ quote: IQuote,
646
+ healthIsFull?: boolean,
647
+ address?: string
648
+ }): Promise<ILeverageMetrics> {
649
+ this._checkLeverageZap();
650
+ address = _getAddress.call(this.llamalend, address);
651
+
652
+
653
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, -1, quote, address);
654
+
655
+ const prices = await this.market._getPrices(_n2, _n1);
656
+ const health = await this._leverageHealth(userCollateral, userBorrowed, debt, -1, quote, healthIsFull, address);
657
+
658
+ return {
659
+ priceImpact: quote.priceImpact,
660
+ bands: [Number(_n2), Number(_n1)],
661
+ prices,
662
+ health,
663
+ }
664
+ }
665
+
666
+ private async _leverageBorrowMore(
667
+ userCollateral: TAmount,
668
+ userBorrowed: TAmount,
669
+ debt: TAmount,
670
+ router: string,
671
+ calldata: string,
672
+ estimateGas: boolean
673
+ ): Promise<string | TGas> {
674
+ if (!(await this.market.userLoanExists())) throw Error("Loan does not exist");
675
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
676
+ const _userBorrowed = parseUnits(userBorrowed, this.market.borrowed_token.decimals);
677
+ const _debt = parseUnits(debt, this.market.borrowed_token.decimals);
678
+
679
+ const zapCalldata = buildCalldataForLeverageZapV2(router, calldata);
680
+
681
+ const contract = this.llamalend.contracts[this.market.addresses.controller].contract;
682
+ const gas = await contract.borrow_more_extended.estimateGas(
683
+ _userCollateral,
684
+ _debt,
685
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
686
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
687
+ zapCalldata,
688
+ { ...this.llamalend.constantOptions }
689
+ );
690
+ if (estimateGas) return smartNumber(gas);
691
+
692
+ await this.llamalend.updateFeeData();
693
+ const gasLimit = _mulBy1_3(DIGas(gas));
694
+
695
+ return (await contract.borrow_more_extended(
696
+ _userCollateral,
697
+ _debt,
698
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
699
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
700
+ zapCalldata,
701
+ { ...this.llamalend.options, gasLimit }
702
+ )).hash
703
+ }
704
+
705
+ public async leverageBorrowMoreIsApproved({ userCollateral, userBorrowed }: {
706
+ userCollateral: TAmount,
707
+ userBorrowed: TAmount
708
+ }): Promise<boolean> {
709
+ return await this.leverageCreateLoanIsApproved({ userCollateral, userBorrowed });
710
+ }
711
+
712
+ public async leverageBorrowMoreApprove({ userCollateral, userBorrowed }: {
713
+ userCollateral: TAmount,
714
+ userBorrowed: TAmount
715
+ }): Promise<string[]> {
716
+ return await this.leverageCreateLoanApprove({ userCollateral, userBorrowed });
717
+ }
718
+
719
+ public async leverageBorrowMoreEstimateGas({ userCollateral, userBorrowed, debt, router, calldata }: {
720
+ userCollateral: TAmount,
721
+ userBorrowed: TAmount,
722
+ debt: TAmount,
723
+ router: string,
724
+ calldata: string
725
+ }): Promise<number> {
726
+ this._checkLeverageZap();
727
+ if (!(await this.leverageCreateLoanIsApproved({ userCollateral, userBorrowed }))) throw Error("Approval is needed for gas estimation");
728
+ return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, router, calldata, true) as number;
729
+ }
730
+
731
+ public async leverageBorrowMore({ userCollateral, userBorrowed, debt, router, calldata }: {
732
+ userCollateral: TAmount,
733
+ userBorrowed: TAmount,
734
+ debt: TAmount,
735
+ router: string,
736
+ calldata: string
737
+ }): Promise<string> {
738
+ this._checkLeverageZap();
739
+ await this.leverageCreateLoanApprove({ userCollateral, userBorrowed });
740
+ return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, router, calldata, false) as string;
741
+ }
742
+
743
+ // ---------------- LEVERAGE REPAY ----------------
744
+
745
+ private _leverageRepayExpectedBorrowed = (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, quote: IQuote):
746
+ { _totalBorrowed: bigint, _borrowedFromStateCollateral: bigint, _borrowedFromUserCollateral: bigint, avgPrice: string } => {
747
+ this._checkLeverageZap();
748
+ const _stateCollateral = parseUnits(stateCollateral, this.market.collateral_token.decimals);
749
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
750
+ let _borrowedExpected = BigInt(0);
751
+ let _borrowedFromStateCollateral = BigInt(0);
752
+ let _borrowedFromUserCollateral = BigInt(0);
753
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
754
+ _borrowedExpected = BigInt(quote.outAmount);
755
+ _borrowedFromStateCollateral = _stateCollateral * BigInt(10 ** 18) / (_stateCollateral + _userCollateral) * _borrowedExpected / BigInt(10 ** 18);
756
+ _borrowedFromUserCollateral = _borrowedExpected - _borrowedFromStateCollateral;
757
+ }
758
+ const _totalBorrowed = _borrowedExpected + parseUnits(userBorrowed, this.market.borrowed_token.decimals);
759
+ const avgPrice = toBN(_borrowedExpected, this.market.borrowed_token.decimals).div(toBN(_stateCollateral + _userCollateral, this.market.collateral_token.decimals)).toString();
760
+
761
+ return { _totalBorrowed, _borrowedFromStateCollateral, _borrowedFromUserCollateral, avgPrice }
762
+ };
763
+
764
+ public leverageRepayExpectedBorrowed = async ({ stateCollateral, userCollateral, userBorrowed, quote }: {
765
+ stateCollateral: TAmount,
766
+ userCollateral: TAmount,
767
+ userBorrowed: TAmount,
768
+ quote: IQuote
769
+ }): Promise<{ totalBorrowed: string, borrowedFromStateCollateral: string, borrowedFromUserCollateral: string, userBorrowed: string, avgPrice: string }> => {
770
+ this._checkLeverageZap();
771
+
772
+ const { _totalBorrowed, _borrowedFromStateCollateral, _borrowedFromUserCollateral, avgPrice } =
773
+ this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed, quote);
774
+
775
+ return {
776
+ totalBorrowed: formatUnits(_totalBorrowed, this.market.borrowed_token.decimals),
777
+ borrowedFromStateCollateral: formatUnits(_borrowedFromStateCollateral, this.market.borrowed_token.decimals),
778
+ borrowedFromUserCollateral: formatUnits(_borrowedFromUserCollateral, this.market.borrowed_token.decimals),
779
+ userBorrowed: formatNumber(userBorrowed, this.market.borrowed_token.decimals),
780
+ avgPrice,
781
+ }
782
+ };
783
+
784
+ public async leverageRepayIsFull({ stateCollateral, userCollateral, userBorrowed, quote, address = "" }: {
785
+ stateCollateral: TAmount,
786
+ userCollateral: TAmount,
787
+ userBorrowed: TAmount,
788
+ quote: IQuote,
789
+ address?: string
790
+ }): Promise<boolean> {
791
+ this._checkLeverageZap();
792
+ address = _getAddress.call(this.llamalend, address);
793
+ const { _borrowed: _stateBorrowed, _debt } = await this.market._userState(address);
794
+ const { _totalBorrowed } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed, quote);
795
+
796
+ return _stateBorrowed + _totalBorrowed > _debt;
797
+ }
798
+
799
+ public async leverageRepayIsAvailable({ stateCollateral, userCollateral, userBorrowed, quote, address = "" }: {
800
+ stateCollateral: TAmount,
801
+ userCollateral: TAmount,
802
+ userBorrowed: TAmount,
803
+ quote: IQuote,
804
+ address?: string
805
+ }): Promise<boolean> {
806
+ // 0. const { collateral, stablecoin, debt } = await this.market.userState(address);
807
+ // 1. maxCollateral for deleverage is collateral from line above.
808
+ // 2. If user is underwater (stablecoin > 0), only full repayment is available:
809
+ // await this.deleverageRepayStablecoins(deleverageCollateral) + stablecoin > debt
810
+ this._checkLeverageZap();
811
+ address = _getAddress.call(this.llamalend, address);
812
+ const { collateral, borrowed, debt } = await this.market.userState(address);
813
+ // Loan does not exist
814
+ if (BN(debt).eq(0)) return false;
815
+ // Can't spend more than user has
816
+ if (BN(stateCollateral).gt(collateral)) return false;
817
+ // Only full repayment and closing the position is available if user is underwater+
818
+ if (BN(borrowed).gt(0)) return await this.leverageRepayIsFull({ stateCollateral, userCollateral, userBorrowed, quote, address });
819
+
820
+ return true;
821
+ }
822
+
823
+ public async leverageRepayExpectedMetrics({ stateCollateral, userCollateral, userBorrowed, healthIsFull, quote, address }: {
824
+ stateCollateral: TAmount,
825
+ userCollateral: TAmount,
826
+ userBorrowed: TAmount,
827
+ healthIsFull: boolean,
828
+ quote: IQuote,
829
+ address: string
830
+ }): Promise<ILeverageMetrics> {
831
+ this._checkLeverageZap();
832
+ address = _getAddress.call(this.llamalend, address);
833
+
834
+ const [_n2, _n1] = await this._leverageRepayBands(stateCollateral, userCollateral, userBorrowed, quote, address);
835
+ const prices = await this.market._getPrices(_n2, _n1);
836
+ const health = await this._leverageRepayHealth(stateCollateral, userCollateral, userBorrowed, quote, healthIsFull, address);
837
+
838
+ const _stateCollateral = parseUnits(stateCollateral, this.market.collateral_token.decimals);
839
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
840
+ const priceImpact = _stateCollateral + _userCollateral > BigInt(0) ? quote.priceImpact : 0;
841
+
842
+ return {
843
+ priceImpact,
844
+ bands: [Number(_n2), Number(_n1)],
845
+ prices,
846
+ health,
847
+ }
848
+ }
849
+
850
+ private _leverageRepayBands = memoize( async (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, quote: IQuote, address: string): Promise<[bigint, bigint]> => {
851
+ address = _getAddress.call(this.llamalend, address);
852
+ if (!(await this.leverageRepayIsAvailable({ stateCollateral, userCollateral, userBorrowed, quote, address }))) return [parseUnits(0, 0), parseUnits(0, 0)];
853
+
854
+ const _stateRepayCollateral = parseUnits(stateCollateral, this.market.collateral_token.decimals);
855
+ const { _collateral: _stateCollateral, _debt: _stateDebt, _N } = await this.market._userState(address);
856
+ if (_stateDebt == BigInt(0)) throw Error(`Loan for ${address} does not exist`);
857
+ if (_stateCollateral < _stateRepayCollateral) throw Error(`Can't use more collateral than user's position has (${_stateRepayCollateral}) > ${_stateCollateral})`);
858
+
859
+ let _n1 = parseUnits(0, 0);
860
+ let _n2 = parseUnits(0, 0);
861
+ const { _totalBorrowed: _repayExpected } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed, quote);
862
+ try {
863
+ _n1 = await this.llamalend.contracts[this.market.addresses.controller].contract.calculate_debt_n1(_stateCollateral - _stateRepayCollateral, _stateDebt - _repayExpected, _N);
864
+ _n2 = _n1 + (_N - BigInt(1));
865
+ return [_n2, _n1];
866
+ } catch {
867
+ return [_n2, _n1];
868
+ }
869
+ },
870
+ {
871
+ promise: true,
872
+ maxAge: 5 * 60 * 1000, // 5m
873
+ });
874
+
875
+
876
+ private async _leverageRepayHealth(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, quote: IQuote, full = true, address = ""): Promise<string> {
877
+ this._checkLeverageZap();
878
+ address = _getAddress.call(this.llamalend, address);
879
+ const { _borrowed: _stateBorrowed, _debt, _N } = await this.market._userState(address);
880
+ if (_stateBorrowed > BigInt(0)) return "0.0";
881
+ if (!(await this.leverageRepayIsAvailable({ stateCollateral, userCollateral, userBorrowed, quote, address }))) return "0.0";
882
+
883
+ const { _totalBorrowed } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed, quote);
884
+ const _dCollateral = parseUnits(stateCollateral, this.market.collateral_token.decimals) * BigInt(-1);
885
+ const _dDebt = _totalBorrowed * BigInt(-1);
886
+
887
+ if (_debt + _dDebt <= BigInt(0)) return "0.0";
888
+ const contract = this.llamalend.contracts[this.market.addresses.controller].contract;
889
+ let _health = await contract.health_calculator(address, _dCollateral, _dDebt, full, _N, this.llamalend.constantOptions) as bigint;
890
+ _health = _health * BigInt(100);
891
+
892
+ return this.llamalend.formatUnits(_health);
893
+ }
894
+
895
+ public async leverageRepayIsApproved({ userCollateral, userBorrowed }: {
896
+ userCollateral: TAmount,
897
+ userBorrowed: TAmount
898
+ }): Promise<boolean> {
899
+ this._checkLeverageZap();
900
+ return await hasAllowance.call(this.llamalend,
901
+ [this.market.collateral_token.address, this.market.borrowed_token.address],
902
+ [userCollateral, userBorrowed],
903
+ this.llamalend.signerAddress,
904
+ this.llamalend.constants.ALIASES.leverage_zap_v2
905
+ );
906
+ }
907
+
908
+ public async leverageRepayApproveEstimateGas ({ userCollateral, userBorrowed }: {
909
+ userCollateral: TAmount,
910
+ userBorrowed: TAmount
911
+ }): Promise<TGas> {
912
+ this._checkLeverageZap();
913
+ return await ensureAllowanceEstimateGas.call(this.llamalend,
914
+ [this.market.collateral_token.address, this.market.borrowed_token.address],
915
+ [userCollateral, userBorrowed],
916
+ this.llamalend.constants.ALIASES.leverage_zap_v2
917
+ );
918
+ }
919
+
920
+ public async leverageRepayApprove({ userCollateral, userBorrowed }: {
921
+ userCollateral: TAmount,
922
+ userBorrowed: TAmount
923
+ }): Promise<string[]> {
924
+ this._checkLeverageZap();
925
+ return await ensureAllowance.call(this.llamalend,
926
+ [this.market.collateral_token.address, this.market.borrowed_token.address],
927
+ [userCollateral, userBorrowed],
928
+ this.llamalend.constants.ALIASES.leverage_zap_v2
929
+ );
930
+ }
931
+
932
+ private async _leverageRepay(
933
+ stateCollateral: TAmount,
934
+ userCollateral: TAmount,
935
+ userBorrowed: TAmount,
936
+ router: string,
937
+ calldata: string,
938
+ estimateGas: boolean
939
+ ): Promise<string | TGas> {
940
+ if (!(await this.market.userLoanExists())) throw Error("Loan does not exist");
941
+ const _stateCollateral = parseUnits(stateCollateral, this.market.collateral_token.decimals);
942
+ const _userCollateral = parseUnits(userCollateral, this.market.collateral_token.decimals);
943
+ const _userBorrowed = parseUnits(userBorrowed, this.market.borrowed_token.decimals);
944
+ let zapCalldata = buildCalldataForLeverageZapV2(router, "0x");
945
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
946
+ zapCalldata = buildCalldataForLeverageZapV2(router, calldata)
947
+ }
948
+
949
+ const contract = this.llamalend.contracts[this.market.addresses.controller].contract;
950
+ const gas = await contract.repay_extended.estimateGas(
951
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
952
+ [0, parseUnits(this._getMarketId(), 0), _userCollateral, _userBorrowed],
953
+ zapCalldata
954
+ );
955
+ if (estimateGas) return smartNumber(gas);
956
+
957
+ await this.llamalend.updateFeeData();
958
+ const gasLimit = _mulBy1_3(DIGas(gas));
959
+
960
+ return (await contract.repay_extended(
961
+ this.llamalend.constants.ALIASES.leverage_zap_v2,
962
+ [0, parseUnits(this._getMarketId(), 0), _userCollateral, _userBorrowed],
963
+ zapCalldata,
964
+ { ...this.llamalend.options, gasLimit }
965
+ )).hash
966
+ }
967
+
968
+ public async leverageRepayEstimateGas({ stateCollateral, userCollateral, userBorrowed, router, calldata }: {
969
+ stateCollateral: TAmount,
970
+ userCollateral: TAmount,
971
+ userBorrowed: TAmount,
972
+ router: string,
973
+ calldata: string
974
+ }): Promise<number> {
975
+ this._checkLeverageZap();
976
+ if (!(await this.leverageRepayIsApproved({ userCollateral, userBorrowed }))) throw Error("Approval is needed for gas estimation");
977
+ return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, router, calldata, true) as number;
978
+ }
979
+
980
+ public async leverageRepay({ stateCollateral, userCollateral, userBorrowed, router, calldata }: {
981
+ stateCollateral: TAmount,
982
+ userCollateral: TAmount,
983
+ userBorrowed: TAmount,
984
+ router: string,
985
+ calldata: string
986
+ }): Promise<string> {
987
+ this._checkLeverageZap();
988
+ await this.leverageRepayApprove({ userCollateral, userBorrowed });
989
+ return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, router, calldata, false) as string;
990
+ }
991
+ }