@curvefi/llamalend-api 1.0.22-beta.2 → 1.0.23

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