@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.
- package/.github/workflows/publish.yml +7 -14
- package/README.md +451 -1
- package/lib/constants/aliases.js +15 -0
- package/lib/interfaces.d.ts +11 -0
- package/lib/lendMarkets/LendMarketTemplate.d.ts +6 -3
- package/lib/lendMarkets/LendMarketTemplate.js +40 -1
- package/lib/lendMarkets/interfaces/leverageZapV2.d.ts +223 -0
- package/lib/lendMarkets/interfaces/leverageZapV2.js +1 -0
- package/lib/lendMarkets/modules/index.d.ts +1 -0
- package/lib/lendMarkets/modules/index.js +1 -0
- package/lib/lendMarkets/modules/leverageZapV2.d.ts +253 -0
- package/lib/lendMarkets/modules/leverageZapV2.js +681 -0
- package/lib/llamalend.js +1 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +5 -0
- package/package.json +2 -1
- package/src/cache/index.ts +1 -1
- package/src/constants/aliases.ts +15 -0
- package/src/external-api.ts +4 -4
- package/src/interfaces.ts +19 -0
- package/src/lendMarkets/LendMarketTemplate.ts +81 -32
- package/src/lendMarkets/interfaces/leverageZapV2.ts +282 -0
- package/src/lendMarkets/modules/index.ts +1 -0
- package/src/lendMarkets/modules/leverageZapV2.ts +991 -0
- package/src/llamalend.ts +1 -0
- package/src/utils.ts +12 -1
|
@@ -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
|
+
}
|