@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.
- package/.github/workflows/publish.yml +1 -1
- package/README.md +346 -0
- package/lib/constants/llammas.js +2 -2
- package/lib/external-api.d.ts +0 -1
- package/lib/external-api.js +0 -6
- package/lib/lendMarkets/LendMarketTemplate.js +1 -1
- package/lib/llamalend.d.ts +0 -1
- package/lib/llamalend.js +28 -34
- package/lib/mintMarkets/MintMarketTemplate.d.ts +21 -4
- package/lib/mintMarkets/MintMarketTemplate.js +52 -0
- package/lib/mintMarkets/interfaces/leverage.d.ts +90 -0
- package/lib/mintMarkets/interfaces/leverage.js +1 -0
- package/lib/mintMarkets/modules/index.d.ts +1 -0
- package/lib/mintMarkets/modules/index.js +1 -0
- package/lib/mintMarkets/modules/leverageV2.d.ts +131 -0
- package/lib/mintMarkets/modules/leverageV2.js +788 -0
- package/package.json +1 -1
- package/src/constants/llammas.ts +2 -2
- package/src/external-api.ts +0 -10
- package/src/lendMarkets/LendMarketTemplate.ts +1 -1
- package/src/llamalend.ts +28 -33
- package/src/mintMarkets/MintMarketTemplate.ts +67 -4
- package/src/mintMarkets/interfaces/leverage.ts +104 -0
- package/src/mintMarkets/modules/index.ts +1 -0
- package/src/mintMarkets/modules/leverageV2.ts +932 -0
|
@@ -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
|
+
}
|