@curvefi/llamalend-api 1.0.2

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.
Files changed (125) hide show
  1. package/.eslintrc.json +40 -0
  2. package/.github/workflows/lint.yml +15 -0
  3. package/.github/workflows/publish.yml +55 -0
  4. package/LICENSE +21 -0
  5. package/README.md +1976 -0
  6. package/lib/cache/index.d.ts +14 -0
  7. package/lib/cache/index.js +31 -0
  8. package/lib/constants/L2Networks.d.ts +1 -0
  9. package/lib/constants/L2Networks.js +1 -0
  10. package/lib/constants/abis/Controller.json +1027 -0
  11. package/lib/constants/abis/ERC20.json +222 -0
  12. package/lib/constants/abis/ERC4626.json +1674 -0
  13. package/lib/constants/abis/GaugeController.json +794 -0
  14. package/lib/constants/abis/GaugeFactoryMainnet.json +1 -0
  15. package/lib/constants/abis/GaugeFactorySidechain.json +475 -0
  16. package/lib/constants/abis/GaugeV5.json +958 -0
  17. package/lib/constants/abis/LeverageZap.json +35 -0
  18. package/lib/constants/abis/Llamma.json +984 -0
  19. package/lib/constants/abis/Minter.json +1 -0
  20. package/lib/constants/abis/MonetaryPolicy.json +221 -0
  21. package/lib/constants/abis/OneWayLendingFactoryABI.json +899 -0
  22. package/lib/constants/abis/SidechainGauge.json +939 -0
  23. package/lib/constants/abis/Vault.json +721 -0
  24. package/lib/constants/abis/crvUSD/DeleverageZap.json +248 -0
  25. package/lib/constants/abis/crvUSD/Factory.json +514 -0
  26. package/lib/constants/abis/crvUSD/HealthCalculatorZap.json +54 -0
  27. package/lib/constants/abis/crvUSD/LeverageZap.json +312 -0
  28. package/lib/constants/abis/crvUSD/MonetaryPolicy.json +294 -0
  29. package/lib/constants/abis/crvUSD/MonetaryPolicy2.json +299 -0
  30. package/lib/constants/abis/crvUSD/PegKeeper.json +411 -0
  31. package/lib/constants/abis/crvUSD/controller.json +991 -0
  32. package/lib/constants/abis/crvUSD/llamma.json +984 -0
  33. package/lib/constants/abis/gas_oracle_optimism.json +149 -0
  34. package/lib/constants/abis/gas_oracle_optimism_blob.json +203 -0
  35. package/lib/constants/aliases.d.ts +16 -0
  36. package/lib/constants/aliases.js +124 -0
  37. package/lib/constants/coins.d.ts +16 -0
  38. package/lib/constants/coins.js +24 -0
  39. package/lib/constants/llammas.d.ts +2 -0
  40. package/lib/constants/llammas.js +96 -0
  41. package/lib/constants/utils.d.ts +4 -0
  42. package/lib/constants/utils.js +27 -0
  43. package/lib/external-api.d.ts +13 -0
  44. package/lib/external-api.js +436 -0
  45. package/lib/index.d.ts +104 -0
  46. package/lib/index.js +123 -0
  47. package/lib/interfaces.d.ts +228 -0
  48. package/lib/interfaces.js +1 -0
  49. package/lib/lendMarkets/LendMarketTemplate.d.ts +510 -0
  50. package/lib/lendMarkets/LendMarketTemplate.js +4682 -0
  51. package/lib/lendMarkets/index.d.ts +3 -0
  52. package/lib/lendMarkets/index.js +3 -0
  53. package/lib/lendMarkets/lendMarketConstructor.d.ts +2 -0
  54. package/lib/lendMarkets/lendMarketConstructor.js +6 -0
  55. package/lib/llamalend.d.ts +80 -0
  56. package/lib/llamalend.js +878 -0
  57. package/lib/mintMarkets/MintMarketTemplate.d.ts +308 -0
  58. package/lib/mintMarkets/MintMarketTemplate.js +2998 -0
  59. package/lib/mintMarkets/index.d.ts +3 -0
  60. package/lib/mintMarkets/index.js +3 -0
  61. package/lib/mintMarkets/mintMarketConstructor.d.ts +2 -0
  62. package/lib/mintMarkets/mintMarketConstructor.js +4 -0
  63. package/lib/st-crvUSD.d.ts +35 -0
  64. package/lib/st-crvUSD.js +505 -0
  65. package/lib/utils.d.ts +58 -0
  66. package/lib/utils.js +661 -0
  67. package/package.json +42 -0
  68. package/src/cache/index.ts +41 -0
  69. package/src/constants/L2Networks.ts +1 -0
  70. package/src/constants/abis/Controller.json +1027 -0
  71. package/src/constants/abis/ERC20.json +222 -0
  72. package/src/constants/abis/ERC4626.json +1674 -0
  73. package/src/constants/abis/GaugeController.json +794 -0
  74. package/src/constants/abis/GaugeFactoryMainnet.json +1 -0
  75. package/src/constants/abis/GaugeFactorySidechain.json +475 -0
  76. package/src/constants/abis/GaugeV5.json +958 -0
  77. package/src/constants/abis/LeverageZap.json +35 -0
  78. package/src/constants/abis/Llamma.json +984 -0
  79. package/src/constants/abis/Minter.json +1 -0
  80. package/src/constants/abis/MonetaryPolicy.json +221 -0
  81. package/src/constants/abis/OneWayLendingFactoryABI.json +899 -0
  82. package/src/constants/abis/SidechainGauge.json +939 -0
  83. package/src/constants/abis/Vault.json +721 -0
  84. package/src/constants/abis/crvUSD/DeleverageZap.json +248 -0
  85. package/src/constants/abis/crvUSD/ERC20.json +222 -0
  86. package/src/constants/abis/crvUSD/Factory.json +514 -0
  87. package/src/constants/abis/crvUSD/HealthCalculatorZap.json +54 -0
  88. package/src/constants/abis/crvUSD/LeverageZap.json +312 -0
  89. package/src/constants/abis/crvUSD/MonetaryPolicy.json +294 -0
  90. package/src/constants/abis/crvUSD/MonetaryPolicy2.json +299 -0
  91. package/src/constants/abis/crvUSD/PegKeeper.json +411 -0
  92. package/src/constants/abis/crvUSD/controller.json +991 -0
  93. package/src/constants/abis/crvUSD/llamma.json +984 -0
  94. package/src/constants/abis/gas_oracle_optimism.json +149 -0
  95. package/src/constants/abis/gas_oracle_optimism_blob.json +203 -0
  96. package/src/constants/aliases.ts +141 -0
  97. package/src/constants/coins.ts +41 -0
  98. package/src/constants/llammas.ts +99 -0
  99. package/src/constants/utils.ts +33 -0
  100. package/src/external-api.ts +325 -0
  101. package/src/index.ts +128 -0
  102. package/src/interfaces.ts +237 -0
  103. package/src/lendMarkets/LendMarketTemplate.ts +3022 -0
  104. package/src/lendMarkets/index.ts +7 -0
  105. package/src/lendMarkets/lendMarketConstructor.ts +7 -0
  106. package/src/llamalend.ts +785 -0
  107. package/src/mintMarkets/MintMarketTemplate.ts +1781 -0
  108. package/src/mintMarkets/index.ts +7 -0
  109. package/src/mintMarkets/mintMarketConstructor.ts +5 -0
  110. package/src/st-crvUSD.ts +244 -0
  111. package/src/utils.ts +497 -0
  112. package/test/fetch.test.ts +152 -0
  113. package/test/general.test.ts +216 -0
  114. package/test/leverageBorrowMore.test.ts +245 -0
  115. package/test/leverageCreateLoan.test.ts +236 -0
  116. package/test/leverageRepay.test.ts +240 -0
  117. package/test/readme.test.ts +475 -0
  118. package/test/selfLiquidate.test.ts +57 -0
  119. package/test/selfLiquidateCrvUSD.test.ts +54 -0
  120. package/test/st_crvUSD.test.ts +68 -0
  121. package/test/swap.test.ts +62 -0
  122. package/test/swapCrvUSD.test.ts +56 -0
  123. package/test/vault.test.ts +112 -0
  124. package/tsconfig.build.json +10 -0
  125. package/tsconfig.json +72 -0
@@ -0,0 +1,3022 @@
1
+ import memoize from "memoizee";
2
+ import BigNumber from "bignumber.js";
3
+ import { llamalend } from "../llamalend.js";
4
+ import {
5
+ _getAddress,
6
+ parseUnits,
7
+ BN,
8
+ toBN,
9
+ fromBN,
10
+ getBalances,
11
+ _ensureAllowance,
12
+ ensureAllowance,
13
+ hasAllowance,
14
+ ensureAllowanceEstimateGas,
15
+ _cutZeros,
16
+ formatUnits,
17
+ formatNumber,
18
+ MAX_ALLOWANCE,
19
+ MAX_ACTIVE_BAND,
20
+ _mulBy1_3,
21
+ _getUsdRate,
22
+ DIGas,
23
+ smartNumber,
24
+ } from "../utils.js";
25
+ import {IDict, TGas, TAmount, IReward, IQuoteOdos, IOneWayMarket} from "../interfaces.js";
26
+ import { _getExpectedOdos, _getQuoteOdos, _assembleTxOdos, _getUserCollateral, _getMarketsData } from "../external-api.js";
27
+ import ERC20Abi from '../constants/abis/ERC20.json' assert { type: 'json' };
28
+ import {cacheKey, cacheStats} from "../cache/index.js";
29
+
30
+
31
+ const DAY = 86400;
32
+ const WEEK = 7 * DAY;
33
+
34
+
35
+ export class LendMarketTemplate {
36
+ id: string;
37
+ name: string
38
+ addresses: {
39
+ amm: string,
40
+ controller: string,
41
+ borrowed_token: string,
42
+ collateral_token: string,
43
+ monetary_policy: string,
44
+ vault: string,
45
+ gauge: string,
46
+ };
47
+ borrowed_token: {
48
+ address: string;
49
+ name: string;
50
+ symbol: string;
51
+ decimals: number;
52
+ };
53
+ collateral_token: {
54
+ address: string;
55
+ name: string;
56
+ symbol: string;
57
+ decimals: number;
58
+ }
59
+ coinAddresses: [string, string]
60
+ coinDecimals: [number, number]
61
+ defaultBands: number
62
+ minBands: number
63
+ maxBands: number
64
+ swapDataCache: IDict<IQuoteOdos> = {}
65
+
66
+ estimateGas: {
67
+ createLoanApprove: (collateral: number | string) => Promise<TGas>,
68
+ createLoan: (collateral: number | string, debt: number | string, range: number) => Promise<TGas>,
69
+ borrowMoreApprove: (collateral: number | string) => Promise<TGas>,
70
+ borrowMore: (collateral: number | string, debt: number | string) => Promise<TGas>,
71
+ addCollateralApprove: (collateral: number | string) => Promise<TGas>,
72
+ addCollateral: (collateral: number | string, address?: string) => Promise<TGas>,
73
+ removeCollateral: (collateral: number | string) => Promise<TGas>,
74
+ repayApprove: (debt: number | string) => Promise<TGas>,
75
+ repay: (debt: number | string, address?: string) => Promise<TGas>,
76
+ fullRepayApprove: (address?: string) => Promise<TGas>,
77
+ fullRepay: (address?: string) => Promise<TGas>,
78
+ swapApprove: (i: number, amount: number | string) => Promise<TGas>,
79
+ swap: (i: number, j: number, amount: number | string, slippage?: number) => Promise<TGas>,
80
+ liquidateApprove: (address: string) => Promise<TGas>,
81
+ liquidate: (address: string, slippage?: number) => Promise<TGas>,
82
+ selfLiquidateApprove: () => Promise<TGas>,
83
+ selfLiquidate: (slippage?: number) => Promise<TGas>,
84
+ };
85
+ stats: {
86
+ parameters: () => Promise<{
87
+ fee: string, // %
88
+ admin_fee: string, // %
89
+ liquidation_discount: string, // %
90
+ loan_discount: string, // %
91
+ base_price: string,
92
+ A: string,
93
+ }>,
94
+ rates: (isGetter?: boolean, useAPI?: boolean) => Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}>,
95
+ futureRates: (dReserves: TAmount, dDebt: TAmount) => Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}>,
96
+ balances: () => Promise<[string, string]>,
97
+ bandsInfo: () => Promise<{ activeBand: number, maxBand: number, minBand: number, liquidationBand: number | null }>
98
+ bandBalances:(n: number) => Promise<{ borrowed: string, collateral: string }>,
99
+ bandsBalances: () => Promise<{ [index: number]: { borrowed: string, collateral: string } }>,
100
+ totalDebt: (isGetter?: boolean, useAPI?: boolean) => Promise<string>,
101
+ ammBalances: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowed: string, collateral: string }>,
102
+ capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ cap: string, available: string }>,
103
+ };
104
+ wallet: {
105
+ balances: (address?: string) => Promise<{ collateral: string, borrowed: string, vaultShares: string, gauge: string }>,
106
+ };
107
+ vault: {
108
+ maxDeposit: (address?: string) => Promise<string>,
109
+ previewDeposit: (amount: TAmount) => Promise<string>,
110
+ depositIsApproved: (borrowed: TAmount) => Promise<boolean>
111
+ depositApprove: (borrowed: TAmount) => Promise<string[]>
112
+ deposit: (amount: TAmount) => Promise<string>,
113
+ maxMint: (address?: string) => Promise<string>,
114
+ previewMint: (amount: TAmount) => Promise<string>,
115
+ mintIsApproved: (borrowed: TAmount) => Promise<boolean>
116
+ mintApprove: (borrowed: TAmount) => Promise<string[]>
117
+ mint: (amount: TAmount) => Promise<string>,
118
+ maxWithdraw: (address?: string) => Promise<string>,
119
+ previewWithdraw: (amount: TAmount) => Promise<string>,
120
+ withdraw: (amount: TAmount) => Promise<string>,
121
+ maxRedeem: (address?: string) => Promise<string>,
122
+ previewRedeem: (amount: TAmount) => Promise<string>,
123
+ redeem: (amount: TAmount) => Promise<string>,
124
+ convertToShares: (assets: TAmount) => Promise<string>,
125
+ convertToAssets: (shares: TAmount) => Promise<string>,
126
+ stakeIsApproved: (vaultShares: number | string) => Promise<boolean>,
127
+ stakeApprove: (vaultShares: number | string) => Promise<string[]>,
128
+ stake: (vaultShares: number | string) => Promise<string>,
129
+ unstake: (vaultShares: number | string) => Promise<string>,
130
+ rewardsOnly: () => boolean,
131
+ totalLiquidity: () => Promise<string>,
132
+ crvApr: (useApi?: boolean) => Promise<[baseApy: number, boostedApy: number]>,
133
+ claimableCrv: (address?: string) => Promise<string>,
134
+ claimCrv: () => Promise<string>,
135
+ rewardTokens: (useApi?: boolean) => Promise<{token: string, symbol: string, decimals: number}[]>,
136
+ rewardsApr: (useApi?: boolean) => Promise<IReward[]>,
137
+ claimableRewards: (address?: string) => Promise<{token: string, symbol: string, amount: string}[]>,
138
+ claimRewards: () => Promise<string>,
139
+ estimateGas: {
140
+ depositApprove: (amount: TAmount) => Promise<TGas>,
141
+ deposit: (amount: TAmount) => Promise<TGas>,
142
+ mintApprove: (amount: TAmount) => Promise<TGas>,
143
+ mint: (amount: TAmount) => Promise<TGas>,
144
+ withdraw: (amount: TAmount) => Promise<TGas>,
145
+ redeem: (amount: TAmount) => Promise<TGas>,
146
+ stakeApprove: (vaultShares: number | string) => Promise<TGas>,
147
+ stake: (vaultShares: number | string) => Promise<TGas>,
148
+ unstake: (vaultShares: number | string) => Promise<TGas>,
149
+ claimCrv: () => Promise<TGas>,
150
+ claimRewards: () => Promise<TGas>,
151
+ }
152
+ };
153
+ leverage: {
154
+ hasLeverage: () => boolean,
155
+
156
+ maxLeverage: (N: number) => Promise<string>,
157
+
158
+ createLoanMaxRecv: (userCollateral: TAmount, userBorrowed: TAmount, range: number) =>
159
+ Promise<{
160
+ maxDebt: string,
161
+ maxTotalCollateral: string,
162
+ userCollateral: string,
163
+ collateralFromUserBorrowed: string,
164
+ collateralFromMaxDebt: string,
165
+ maxLeverage: string,
166
+ avgPrice: string,
167
+ }>,
168
+ createLoanMaxRecvAllRanges: (userCollateral: TAmount, userBorrowed: TAmount) =>
169
+ Promise<IDict<{
170
+ maxDebt: string,
171
+ maxTotalCollateral: string,
172
+ userCollateral: string,
173
+ collateralFromUserBorrowed: string,
174
+ collateralFromMaxDebt: string,
175
+ maxLeverage: string,
176
+ avgPrice: string,
177
+ }>>,
178
+ createLoanExpectedCollateral: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage?: number) =>
179
+ Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, leverage: string, avgPrice: string }>,
180
+ createLoanPriceImpact: (userBorrowed: TAmount, debt: TAmount) => Promise<string>,
181
+ createLoanMaxRange: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise<number>,
182
+ createLoanBands: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number) => Promise<[number, number]>,
183
+ createLoanBandsAllRanges: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise<IDict<[number, number] | null>>,
184
+ createLoanPrices: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number) => Promise<string[]>,
185
+ createLoanPricesAllRanges: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise<IDict<[string, string] | null>>,
186
+ createLoanHealth: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, full?: boolean) => Promise<string>,
187
+ createLoanIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<boolean>,
188
+ createLoanApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<string[]>,
189
+ createLoanRouteImage: (userBorrowed: TAmount, debt: TAmount) => Promise<string>,
190
+ createLoan: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage?: number) => Promise<string>,
191
+
192
+ borrowMoreMaxRecv: (userCollateral: TAmount, userBorrowed: TAmount, address?: string) =>
193
+ Promise<{
194
+ maxDebt: string,
195
+ maxTotalCollateral: string,
196
+ userCollateral: string,
197
+ collateralFromUserBorrowed: string,
198
+ collateralFromMaxDebt: string,
199
+ avgPrice: string,
200
+ }>,
201
+ borrowMoreExpectedCollateral: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, slippage?: number, address?: string) =>
202
+ Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, avgPrice: string }>,
203
+ borrowMorePriceImpact: (userBorrowed: TAmount, dDebt: TAmount, address?: string) => Promise<string>,
204
+ borrowMoreBands: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address?: string) => Promise<[number, number]>,
205
+ borrowMorePrices: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address?: string) => Promise<string[]>,
206
+ borrowMoreHealth: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, full?: boolean, address?: string) => Promise<string>,
207
+ borrowMoreIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<boolean>,
208
+ borrowMoreApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<string[]>,
209
+ borrowMoreRouteImage: (userBorrowed: TAmount, debt: TAmount) => Promise<string>,
210
+ borrowMore: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise<string>,
211
+
212
+ repayExpectedBorrowed: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage?: number) =>
213
+ Promise<{ totalBorrowed: string, borrowedFromStateCollateral: string, borrowedFromUserCollateral: string, userBorrowed: string, avgPrice: string }>,
214
+ repayPriceImpact: (stateCollateral: TAmount, userCollateral: TAmount) => Promise<string>,
215
+ repayIsFull: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise<boolean>,
216
+ repayIsAvailable: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise<boolean>,
217
+ repayBands: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise<[number, number]>,
218
+ repayPrices: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise<string[]>,
219
+ repayHealth: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, full?: boolean, address?: string) => Promise<string>,
220
+ repayIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<boolean>,
221
+ repayApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<string[]>,
222
+ repayRouteImage: (stateCollateral: TAmount, userCollateral: TAmount) => Promise<string>,
223
+ repay: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage?: number) => Promise<string>,
224
+
225
+ estimateGas: {
226
+ createLoanApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<TGas>,
227
+ createLoan: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage?: number) => Promise<number>,
228
+
229
+ borrowMoreApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<TGas>,
230
+ borrowMore: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise<number>,
231
+
232
+ repayApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise<TGas>,
233
+ repay: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage?: number) => Promise<number>,
234
+ }
235
+ };
236
+
237
+ constructor(id: string, marketData: IOneWayMarket) {
238
+ this.id = id;
239
+ this.name = marketData.name;
240
+ this.addresses = marketData.addresses;
241
+ this.borrowed_token = marketData.borrowed_token;
242
+ this.collateral_token = marketData.collateral_token;
243
+ this.coinDecimals = [this.borrowed_token.decimals, this.collateral_token.decimals]
244
+ this.coinAddresses = [this.borrowed_token.address, this.collateral_token.address]
245
+ this.defaultBands = 10
246
+ this.minBands = 4
247
+ this.maxBands = 50
248
+ this.estimateGas = {
249
+ createLoanApprove: this.createLoanApproveEstimateGas.bind(this),
250
+ createLoan: this.createLoanEstimateGas.bind(this),
251
+ borrowMoreApprove: this.borrowMoreApproveEstimateGas.bind(this),
252
+ borrowMore: this.borrowMoreEstimateGas.bind(this),
253
+ addCollateralApprove: this.addCollateralApproveEstimateGas.bind(this),
254
+ addCollateral: this.addCollateralEstimateGas.bind(this),
255
+ removeCollateral: this.removeCollateralEstimateGas.bind(this),
256
+ repayApprove: this.repayApproveEstimateGas.bind(this),
257
+ repay: this.repayEstimateGas.bind(this),
258
+ fullRepayApprove: this.fullRepayApproveEstimateGas.bind(this),
259
+ fullRepay: this.fullRepayEstimateGas.bind(this),
260
+ swapApprove: this.swapApproveEstimateGas.bind(this),
261
+ swap: this.swapEstimateGas.bind(this),
262
+ liquidateApprove: this.liquidateApproveEstimateGas.bind(this),
263
+ liquidate: this.liquidateEstimateGas.bind(this),
264
+ selfLiquidateApprove: this.selfLiquidateApproveEstimateGas.bind(this),
265
+ selfLiquidate: this.selfLiquidateEstimateGas.bind(this),
266
+ }
267
+ this.stats = {
268
+ parameters: this.statsParameters.bind(this),
269
+ rates: this.statsRates.bind(this),
270
+ futureRates: this.statsFutureRates.bind(this),
271
+ balances: this.statsBalances.bind(this),
272
+ bandsInfo: this.statsBandsInfo.bind(this),
273
+ bandBalances: this.statsBandBalances.bind(this),
274
+ bandsBalances: this.statsBandsBalances.bind(this),
275
+ totalDebt: this.statsTotalDebt.bind(this),
276
+ ammBalances: this.statsAmmBalances.bind(this),
277
+ capAndAvailable: this.statsCapAndAvailable.bind(this),
278
+ }
279
+ this.wallet = {
280
+ balances: this.walletBalances.bind(this),
281
+ }
282
+ this.vault = {
283
+ maxDeposit: this.vaultMaxDeposit.bind(this),
284
+ previewDeposit: this.vaultPreviewDeposit.bind(this),
285
+ depositIsApproved: this.vaultDepositIsApproved.bind(this),
286
+ depositApprove: this.vaultDepositApprove.bind(this),
287
+ deposit: this.vaultDeposit.bind(this),
288
+ maxMint: this.vaultMaxMint.bind(this),
289
+ previewMint: this.vaultPreviewMint.bind(this),
290
+ mintIsApproved: this.vaultMintIsApproved.bind(this),
291
+ mintApprove: this.vaultMintApprove.bind(this),
292
+ mint: this.vaultMint.bind(this),
293
+ maxWithdraw: this.vaultMaxWithdraw.bind(this),
294
+ previewWithdraw: this.vaultPreviewWithdraw.bind(this),
295
+ withdraw: this.vaultWithdraw.bind(this),
296
+ maxRedeem: this.vaultMaxRedeem.bind(this),
297
+ previewRedeem: this.vaultPreviewRedeem.bind(this),
298
+ redeem: this.vaultRedeem.bind(this),
299
+ convertToShares: this.vaultConvertToShares.bind(this),
300
+ convertToAssets: this.vaultConvertToAssets.bind(this),
301
+ stakeIsApproved: this.vaultStakeIsApproved.bind(this),
302
+ stakeApprove: this.vaultStakeApprove.bind(this),
303
+ stake: this.vaultStake.bind(this),
304
+ unstake: this.vaultUnstake.bind(this),
305
+ rewardsOnly: this.vaultRewardsOnly.bind(this),
306
+ totalLiquidity: this.vaultTotalLiquidity.bind(this),
307
+ crvApr: this.vaultCrvApr.bind(this),
308
+ claimableCrv: this.vaultClaimableCrv.bind(this),
309
+ claimCrv: this.vaultClaimCrv.bind(this),
310
+ rewardTokens: this.vaultRewardTokens.bind(this),
311
+ rewardsApr: this.vaultRewardsApr.bind(this),
312
+ claimableRewards: this.vaultClaimableRewards.bind(this),
313
+ claimRewards: this.vaultClaimRewards.bind(this),
314
+ estimateGas: {
315
+ depositApprove: this.vaultDepositApproveEstimateGas.bind(this),
316
+ deposit: this.vaultDepositEstimateGas.bind(this),
317
+ mintApprove: this.vaultMintApproveEstimateGas.bind(this),
318
+ mint: this.vaultMintEstimateGas.bind(this),
319
+ withdraw: this.vaultWithdrawEstimateGas.bind(this),
320
+ redeem: this.vaultRedeemEstimateGas.bind(this),
321
+ stakeApprove: this.vaultStakeApproveEstimateGas.bind(this),
322
+ stake: this.vaultStakeEstimateGas.bind(this),
323
+ unstake: this.vaultUnstakeEstimateGas.bind(this),
324
+ claimCrv: this.vaultClaimCrvEstimateGas.bind(this),
325
+ claimRewards: this.vaultClaimRewardsEstimateGas.bind(this),
326
+ },
327
+ }
328
+ this.leverage = {
329
+ hasLeverage: this.hasLeverage.bind(this),
330
+
331
+ maxLeverage: this.maxLeverage.bind(this),
332
+
333
+ createLoanMaxRecv: this.leverageCreateLoanMaxRecv.bind(this),
334
+ createLoanMaxRecvAllRanges: this.leverageCreateLoanMaxRecvAllRanges.bind(this),
335
+ createLoanExpectedCollateral: this.leverageCreateLoanExpectedCollateral.bind(this),
336
+ createLoanPriceImpact: this.leverageCreateLoanPriceImpact.bind(this),
337
+ createLoanMaxRange: this.leverageCreateLoanMaxRange.bind(this),
338
+ createLoanBands: this.leverageCreateLoanBands.bind(this),
339
+ createLoanBandsAllRanges: this.leverageCreateLoanBandsAllRanges.bind(this),
340
+ createLoanPrices: this.leverageCreateLoanPrices.bind(this),
341
+ createLoanPricesAllRanges: this.leverageCreateLoanPricesAllRanges.bind(this),
342
+ createLoanHealth: this.leverageCreateLoanHealth.bind(this),
343
+ createLoanIsApproved: this.leverageCreateLoanIsApproved.bind(this),
344
+ createLoanApprove: this.leverageCreateLoanApprove.bind(this),
345
+ createLoanRouteImage: this.leverageCreateLoanRouteImage.bind(this),
346
+ createLoan: this.leverageCreateLoan.bind(this),
347
+
348
+ borrowMoreMaxRecv: this.leverageBorrowMoreMaxRecv.bind(this),
349
+ borrowMoreExpectedCollateral: this.leverageBorrowMoreExpectedCollateral.bind(this),
350
+ borrowMorePriceImpact: this.leverageBorrowMorePriceImpact.bind(this),
351
+ borrowMoreBands: this.leverageBorrowMoreBands.bind(this),
352
+ borrowMorePrices: this.leverageBorrowMorePrices.bind(this),
353
+ borrowMoreHealth: this.leverageBorrowMoreHealth.bind(this),
354
+ borrowMoreIsApproved: this.leverageCreateLoanIsApproved.bind(this),
355
+ borrowMoreApprove: this.leverageCreateLoanApprove.bind(this),
356
+ borrowMoreRouteImage: this.leverageBorrowMoreRouteImage.bind(this),
357
+ borrowMore: this.leverageBorrowMore.bind(this),
358
+
359
+ repayExpectedBorrowed: this.leverageRepayExpectedBorrowed.bind(this),
360
+ repayPriceImpact: this.leverageRepayPriceImpact.bind(this),
361
+ repayIsFull: this.leverageRepayIsFull.bind(this),
362
+ repayIsAvailable: this.leverageRepayIsAvailable.bind(this),
363
+ repayBands: this.leverageRepayBands.bind(this),
364
+ repayPrices: this.leverageRepayPrices.bind(this),
365
+ repayHealth: this.leverageRepayHealth.bind(this),
366
+ repayIsApproved: this.leverageRepayIsApproved.bind(this),
367
+ repayApprove: this.leverageRepayApprove.bind(this),
368
+ repayRouteImage: this.leverageRepayRouteImage.bind(this),
369
+ repay: this.leverageRepay.bind(this),
370
+
371
+ estimateGas: {
372
+ createLoanApprove: this.leverageCreateLoanApproveEstimateGas.bind(this),
373
+ createLoan: this.leverageCreateLoanEstimateGas.bind(this),
374
+
375
+ borrowMoreApprove: this.leverageCreateLoanApproveEstimateGas.bind(this),
376
+ borrowMore: this.leverageBorrowMoreEstimateGas.bind(this),
377
+
378
+ repayApprove: this.leverageRepayApproveEstimateGas.bind(this),
379
+ repay: this.leverageRepayEstimateGas.bind(this),
380
+ },
381
+ }
382
+
383
+ }
384
+
385
+ private _getMarketId = (): number => Number(this.id.split("-").slice(-1)[0]);
386
+
387
+ // ---------------- VAULT ----------------
388
+
389
+ private async vaultMaxDeposit(address = ""): Promise<string> {
390
+ address = _getAddress(address);
391
+ // const _amount = await llamalend.contracts[this.addresses.vault].contract.maxDeposit(address); TODO use maxDeposit
392
+ const _amount = await llamalend.contracts[this.addresses.borrowed_token].contract.balanceOf(address);
393
+
394
+ return formatUnits(_amount, this.borrowed_token.decimals);
395
+ }
396
+
397
+ private async vaultPreviewDeposit(amount: TAmount): Promise<string> {
398
+ const _amount = parseUnits(amount, this.borrowed_token.decimals);
399
+ const _shares = await llamalend.contracts[this.addresses.vault].contract.previewDeposit(_amount);
400
+
401
+ return formatUnits(_shares, 18);
402
+ }
403
+
404
+ private async vaultDepositIsApproved(borrowed: TAmount): Promise<boolean> {
405
+ return await hasAllowance([this.borrowed_token.address], [borrowed], llamalend.signerAddress, this.addresses.vault);
406
+ }
407
+
408
+ private async vaultDepositApproveEstimateGas (borrowed: TAmount): Promise<TGas> {
409
+ return await ensureAllowanceEstimateGas([this.borrowed_token.address], [borrowed], this.addresses.vault);
410
+ }
411
+
412
+ private async vaultDepositApprove(borrowed: TAmount): Promise<string[]> {
413
+ return await ensureAllowance([this.borrowed_token.address], [borrowed], this.addresses.vault);
414
+ }
415
+
416
+ private async _vaultDeposit(amount: TAmount, estimateGas = false): Promise<string | TGas> {
417
+ const _amount = parseUnits(amount, this.borrowed_token.decimals);
418
+ const gas = await llamalend.contracts[this.addresses.vault].contract.deposit.estimateGas(_amount, { ...llamalend.constantOptions });
419
+ if (estimateGas) return smartNumber(gas);
420
+
421
+ await llamalend.updateFeeData();
422
+
423
+ const gasLimit = _mulBy1_3(DIGas(gas));
424
+
425
+ return (await llamalend.contracts[this.addresses.vault].contract.deposit(_amount, { ...llamalend.options, gasLimit })).hash;
426
+ }
427
+
428
+ private async vaultDepositEstimateGas(amount: TAmount): Promise<TGas> {
429
+ if (!(await this.vaultDepositIsApproved(amount))) throw Error("Approval is needed for gas estimation");
430
+ return await this._vaultDeposit(amount, true) as number;
431
+ }
432
+
433
+ private async vaultDeposit(amount: TAmount): Promise<string> {
434
+ await this.vaultDepositApprove(amount);
435
+ return await this._vaultDeposit(amount, false) as string;
436
+ }
437
+
438
+
439
+ private async vaultMaxMint(address = ""): Promise<string> {
440
+ address = _getAddress(address);
441
+ // const _shares = await llamalend.contracts[this.addresses.vault].contract.maxMint(address); TODO use maxMint
442
+ const _assetBalance = await llamalend.contracts[this.addresses.borrowed_token].contract.balanceOf(address);
443
+ const _shares = await llamalend.contracts[this.addresses.vault].contract.convertToShares(_assetBalance);
444
+
445
+ return formatUnits(_shares, 18);
446
+ }
447
+
448
+ private async vaultPreviewMint(amount: TAmount): Promise<string> {
449
+ const _amount = parseUnits(amount, 18);
450
+ const _assets = await llamalend.contracts[this.addresses.vault].contract.previewMint(_amount);
451
+
452
+ return formatUnits(_assets, this.borrowed_token.decimals);
453
+ }
454
+
455
+ private async vaultMintIsApproved(borrowed: TAmount): Promise<boolean> {
456
+ return await hasAllowance([this.borrowed_token.address], [borrowed], llamalend.signerAddress, this.addresses.vault);
457
+ }
458
+
459
+ private async vaultMintApproveEstimateGas (borrowed: TAmount): Promise<TGas> {
460
+ return await ensureAllowanceEstimateGas([this.borrowed_token.address], [borrowed], this.addresses.vault);
461
+ }
462
+
463
+ private async vaultMintApprove(borrowed: TAmount): Promise<string[]> {
464
+ return await ensureAllowance([this.borrowed_token.address], [borrowed], this.addresses.vault);
465
+ }
466
+
467
+ private async _vaultMint(amount: TAmount, estimateGas = false): Promise<string | TGas> {
468
+ const _amount = parseUnits(amount, 18);
469
+ const gas = await llamalend.contracts[this.addresses.vault].contract.mint.estimateGas(_amount, { ...llamalend.constantOptions });
470
+ if (estimateGas) return smartNumber(gas);
471
+
472
+ await llamalend.updateFeeData();
473
+
474
+ const gasLimit = _mulBy1_3(DIGas(gas));
475
+
476
+ return (await llamalend.contracts[this.addresses.vault].contract.mint(_amount, { ...llamalend.options, gasLimit })).hash;
477
+ }
478
+
479
+ private async vaultMintEstimateGas(amount: TAmount): Promise<TGas> {
480
+ if (!(await this.vaultMintIsApproved(amount))) throw Error("Approval is needed for gas estimation");
481
+ return await this._vaultMint(amount, true) as number;
482
+ }
483
+
484
+ private async vaultMint(amount: TAmount): Promise<string> {
485
+ await this.vaultMintApprove(amount);
486
+ return await this._vaultMint(amount, false) as string;
487
+ }
488
+
489
+
490
+ private async vaultMaxWithdraw(address = ""): Promise<string> {
491
+ address = _getAddress(address);
492
+ const _assets = await llamalend.contracts[this.addresses.vault].contract.maxWithdraw(address);
493
+
494
+ return formatUnits(_assets, this.borrowed_token.decimals);
495
+ }
496
+
497
+ private async vaultPreviewWithdraw(amount: TAmount): Promise<string> {
498
+ const _amount = parseUnits(amount, this.borrowed_token.decimals);
499
+ const _shares = await llamalend.contracts[this.addresses.vault].contract.previewWithdraw(_amount);
500
+
501
+ return formatUnits(_shares, 18);
502
+ }
503
+
504
+ private async _vaultWithdraw(amount: TAmount, estimateGas = false): Promise<string | TGas> {
505
+ const _amount = parseUnits(amount, this.borrowed_token.decimals);
506
+ const gas = await llamalend.contracts[this.addresses.vault].contract.withdraw.estimateGas(_amount, { ...llamalend.constantOptions });
507
+ if (estimateGas) return smartNumber(gas);
508
+
509
+ await llamalend.updateFeeData();
510
+
511
+ const gasLimit = _mulBy1_3(DIGas(gas));
512
+
513
+ return (await llamalend.contracts[this.addresses.vault].contract.withdraw(_amount, { ...llamalend.options, gasLimit })).hash;
514
+ }
515
+
516
+ private async vaultWithdrawEstimateGas(amount: TAmount): Promise<TGas> {
517
+ return await this._vaultWithdraw(amount, true) as number;
518
+ }
519
+
520
+ private async vaultWithdraw(amount: TAmount): Promise<string> {
521
+ return await this._vaultWithdraw(amount, false) as string;
522
+ }
523
+
524
+
525
+ private async vaultMaxRedeem(address = ""): Promise<string> {
526
+ address = _getAddress(address);
527
+ const _shares = await llamalend.contracts[this.addresses.vault].contract.maxRedeem(address)
528
+
529
+ return formatUnits(_shares, 18);
530
+ }
531
+
532
+ private async vaultPreviewRedeem(amount: TAmount): Promise<string> {
533
+ const _amount = parseUnits(amount, 18);
534
+ const _assets = await llamalend.contracts[this.addresses.vault].contract.previewRedeem(_amount);
535
+
536
+ return formatUnits(_assets, this.borrowed_token.decimals);
537
+ }
538
+
539
+ private async _vaultRedeem(amount: TAmount, estimateGas = false): Promise<string | TGas> {
540
+ const _amount = parseUnits(amount, 18);
541
+ const gas = await llamalend.contracts[this.addresses.vault].contract.redeem.estimateGas(_amount, { ...llamalend.constantOptions });
542
+ if (estimateGas) return smartNumber(gas);
543
+
544
+ await llamalend.updateFeeData();
545
+
546
+ const gasLimit = _mulBy1_3(DIGas(gas));
547
+
548
+ return (await llamalend.contracts[this.addresses.vault].contract.redeem(_amount, { ...llamalend.options, gasLimit })).hash;
549
+ }
550
+
551
+ private async vaultRedeemEstimateGas(amount: TAmount): Promise<TGas> {
552
+ return await this._vaultRedeem(amount, true) as number;
553
+ }
554
+
555
+ private async vaultRedeem(amount: TAmount): Promise<string> {
556
+ return await this._vaultRedeem(amount, false) as string;
557
+ }
558
+
559
+ // ---------------- VAULT UTILS ----------------
560
+
561
+ private async vaultConvertToShares(assets: TAmount): Promise<string> {
562
+ const _assets = parseUnits(assets, this.borrowed_token.decimals);
563
+ const _shares = await llamalend.contracts[this.addresses.vault].contract.convertToShares(_assets);
564
+
565
+ return llamalend.formatUnits(_shares);
566
+ }
567
+
568
+ private async vaultConvertToAssets(shares: TAmount): Promise<string> {
569
+ const _shares = parseUnits(shares);
570
+ const _assets = await llamalend.contracts[this.addresses.vault].contract.convertToAssets(_shares);
571
+
572
+ return llamalend.formatUnits(_assets, this.borrowed_token.decimals);
573
+ }
574
+
575
+ // ---------------- VAULT STAKING ----------------
576
+
577
+ private async vaultStakeIsApproved(vaultShares: number | string): Promise<boolean> {
578
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
579
+ throw Error(`stakeIsApproved method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
580
+ }
581
+ return await hasAllowance([this.addresses.vault], [vaultShares], llamalend.signerAddress, this.addresses.gauge);
582
+ }
583
+
584
+ private async vaultStakeApproveEstimateGas(vaultShares: number | string): Promise<TGas> {
585
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
586
+ throw Error(`stakeApproveEstimateGas method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
587
+ }
588
+ return await ensureAllowanceEstimateGas([this.addresses.vault], [vaultShares], this.addresses.gauge);
589
+ }
590
+
591
+ private async vaultStakeApprove(vaultShares: number | string): Promise<string[]> {
592
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
593
+ throw Error(`stakeApprove method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
594
+ }
595
+ return await ensureAllowance([this.addresses.vault], [vaultShares], this.addresses.gauge);
596
+ }
597
+
598
+ private async vaultStakeEstimateGas(vaultShares: number | string): Promise<TGas> {
599
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
600
+ throw Error(`stakeEstimateGas method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
601
+ }
602
+ const _vaultShares = parseUnits(vaultShares);
603
+ return smartNumber(await llamalend.contracts[this.addresses.gauge].contract.deposit.estimateGas(_vaultShares, llamalend.constantOptions));
604
+ }
605
+
606
+ private async vaultStake(vaultShares: number | string): Promise<string> {
607
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
608
+ throw Error(`stake method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
609
+ }
610
+ const _vaultShares = parseUnits(vaultShares);
611
+ await _ensureAllowance([this.addresses.vault], [_vaultShares], this.addresses.gauge)
612
+
613
+ await llamalend.updateFeeData();
614
+ const gasLimit = _mulBy1_3(DIGas(await llamalend.contracts[this.addresses.gauge].contract.deposit.estimateGas(_vaultShares, llamalend.constantOptions)));
615
+ return (await llamalend.contracts[this.addresses.gauge].contract.deposit(_vaultShares, { ...llamalend.options, gasLimit })).hash;
616
+ }
617
+
618
+ private async vaultUnstakeEstimateGas(vaultShares: number | string): Promise<TGas> {
619
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
620
+ throw Error(`unstakeEstimateGas method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
621
+ }
622
+ const _vaultShares = parseUnits(vaultShares);
623
+ return smartNumber(await llamalend.contracts[this.addresses.gauge].contract.withdraw.estimateGas(_vaultShares, llamalend.constantOptions));
624
+ }
625
+
626
+ private async vaultUnstake(vaultShares: number | string): Promise<string> {
627
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
628
+ throw Error(`unstake method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
629
+ }
630
+ const _vaultShares = parseUnits(vaultShares);
631
+
632
+ await llamalend.updateFeeData();
633
+ const gasLimit = _mulBy1_3(DIGas((await llamalend.contracts[this.addresses.gauge].contract.withdraw.estimateGas(_vaultShares, llamalend.constantOptions))));
634
+ return (await llamalend.contracts[this.addresses.gauge].contract.withdraw(_vaultShares, { ...llamalend.options, gasLimit })).hash;
635
+ }
636
+
637
+ // ---------------- VAULT STAKING REWARDS ----------------
638
+
639
+ private vaultRewardsOnly(): boolean {
640
+ if (llamalend.chainId === 2222 || llamalend.chainId === 324) return true; // TODO remove this for Kava and ZkSync
641
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) throw Error(`${this.name} doesn't have gauge`);
642
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].contract;
643
+
644
+ return !('inflation_rate()' in gaugeContract || 'inflation_rate(uint256)' in gaugeContract);
645
+ }
646
+
647
+ private async vaultTotalLiquidity(useAPI = true): Promise<string> {
648
+ const { cap } = await this.statsCapAndAvailable(true, useAPI);
649
+ const price = await _getUsdRate(this.addresses.borrowed_token);
650
+
651
+ return BN(cap).times(price).toFixed(6)
652
+ }
653
+
654
+ private _calcCrvApr = async (futureWorkingSupplyBN: BigNumber | null = null): Promise<[baseApy: number, boostedApy: number]> => {
655
+ const totalLiquidityUSD = await this.vaultTotalLiquidity();
656
+ if (Number(totalLiquidityUSD) === 0) return [0, 0];
657
+
658
+ let inflationRateBN, workingSupplyBN, totalSupplyBN;
659
+ if (llamalend.chainId !== 1) {
660
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].multicallContract;
661
+ const lpTokenContract = llamalend.contracts[this.addresses.vault].multicallContract;
662
+ const crvContract = llamalend.contracts[llamalend.constants.ALIASES.crv].contract;
663
+
664
+ const currentWeek = Math.floor(Date.now() / 1000 / WEEK);
665
+ [inflationRateBN, workingSupplyBN, totalSupplyBN] = (await llamalend.multicallProvider.all([
666
+ gaugeContract.inflation_rate(currentWeek),
667
+ gaugeContract.working_supply(),
668
+ lpTokenContract.totalSupply(),
669
+ ]) as bigint[]).map((value) => toBN(value));
670
+
671
+ if (inflationRateBN.eq(0)) {
672
+ inflationRateBN = toBN(await crvContract.balanceOf(this.addresses.gauge, llamalend.constantOptions)).div(WEEK);
673
+ }
674
+ } else {
675
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].multicallContract;
676
+ const lpTokenContract = llamalend.contracts[this.addresses.vault].multicallContract;
677
+ const gaugeControllerContract = llamalend.contracts[llamalend.constants.ALIASES.gauge_controller].multicallContract;
678
+
679
+ let weightBN;
680
+ [inflationRateBN, weightBN, workingSupplyBN, totalSupplyBN] = (await llamalend.multicallProvider.all([
681
+ gaugeContract.inflation_rate(),
682
+ gaugeControllerContract.gauge_relative_weight(this.addresses.gauge),
683
+ gaugeContract.working_supply(),
684
+ lpTokenContract.totalSupply(),
685
+ ]) as bigint[]).map((value) => toBN(value));
686
+
687
+ inflationRateBN = inflationRateBN.times(weightBN);
688
+ }
689
+
690
+ if (inflationRateBN.eq(0)) return [0, 0];
691
+ if (futureWorkingSupplyBN !== null) workingSupplyBN = futureWorkingSupplyBN;
692
+
693
+ // If you added 1$ value of LP it would be 0.4$ of working LP. So your annual reward per 1$ in USD is:
694
+ // (annual reward per working liquidity in $) * (0.4$ of working LP)
695
+ const rateBN = inflationRateBN.times(31536000).div(workingSupplyBN).times(totalSupplyBN).div(Number(totalLiquidityUSD)).times(0.4);
696
+ const crvPrice = await _getUsdRate(llamalend.constants.ALIASES.crv);
697
+ const baseApyBN = rateBN.times(crvPrice);
698
+ const boostedApyBN = baseApyBN.times(2.5);
699
+
700
+ return [baseApyBN.times(100).toNumber(), boostedApyBN.times(100).toNumber()]
701
+ }
702
+
703
+ private async vaultCrvApr(useApi = true): Promise<[baseApy: number, boostedApy: number]> {
704
+ if (this.vaultRewardsOnly()) throw Error(`${this.name} has Rewards-Only Gauge. Use stats.rewardsApy instead`);
705
+
706
+ // const isDisabledChain = [1313161554].includes(llamalend.chainId); // Disable Aurora
707
+ // if (useApi && !isDisabledChain) {
708
+ // const crvAPYs = await _getCrvApyFromApi();
709
+ // const poolCrvApy = crvAPYs[this.addresses.gauge] ?? [0, 0]; // new pools might be missing
710
+ // return [poolCrvApy[0], poolCrvApy[1]];
711
+ // }
712
+
713
+ return await this._calcCrvApr();
714
+ }
715
+
716
+ private async vaultClaimableCrv (address = ""): Promise<string> {
717
+ if (this.vaultRewardsOnly()) throw Error(`${this.name} has Rewards-Only Gauge. Use claimableRewards instead`);
718
+ address = address || llamalend.signerAddress;
719
+ if (!address) throw Error("Need to connect wallet or pass address into args");
720
+
721
+ return llamalend.formatUnits(await llamalend.contracts[this.addresses.gauge].contract.claimable_tokens(address, llamalend.constantOptions));
722
+ }
723
+
724
+ private async _vaultClaimCrv(estimateGas: boolean): Promise<string | TGas> {
725
+ if (this.vaultRewardsOnly()) throw Error(`${this.name} has Rewards-Only Gauge. Use claimRewards instead`);
726
+ const contract = llamalend.contracts[llamalend.constants.ALIASES.minter].contract;
727
+ const gas = await contract.mint.estimateGas(this.addresses.gauge, llamalend.constantOptions);
728
+ if (estimateGas) return smartNumber(gas);
729
+
730
+ await llamalend.updateFeeData();
731
+ const gasLimit = _mulBy1_3(DIGas(gas));
732
+ return (await contract.mint(this.addresses.gauge, { ...llamalend.options, gasLimit })).hash
733
+ }
734
+
735
+ private async vaultClaimCrvEstimateGas(): Promise<TGas> {
736
+ return await this._vaultClaimCrv(true) as TGas;
737
+ }
738
+
739
+ private async vaultClaimCrv(): Promise<string> {
740
+ return await this._vaultClaimCrv(false) as string;
741
+ }
742
+
743
+ private vaultRewardTokens = memoize(async (useApi = true): Promise<{token: string, symbol: string, decimals: number}[]> => {
744
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) return []
745
+
746
+ // if (useApi) {
747
+ // const rewards = await _getRewardsFromApi();
748
+ // if (!rewards[this.addresses.gauge]) return [];
749
+ // rewards[this.addresses.gauge].forEach((r) => _setContracts(r.tokenAddress, ERC20Abi));
750
+ // return rewards[this.addresses.gauge].map((r) => ({ token: r.tokenAddress, symbol: r.symbol, decimals: Number(r.decimals) }));
751
+ // }
752
+
753
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].contract;
754
+ const gaugeMulticallContract = llamalend.contracts[this.addresses.gauge].multicallContract;
755
+ const rewardCount = Number(llamalend.formatUnits(await gaugeContract.reward_count(llamalend.constantOptions), 0));
756
+
757
+ const tokenCalls = [];
758
+ for (let i = 0; i < rewardCount; i++) {
759
+ tokenCalls.push(gaugeMulticallContract.reward_tokens(i));
760
+ }
761
+ const tokens = (await llamalend.multicallProvider.all(tokenCalls) as string[])
762
+ .filter((addr) => addr !== llamalend.constants.ZERO_ADDRESS)
763
+ .map((addr) => addr.toLowerCase())
764
+ .filter((addr) => llamalend.chainId === 1 || addr !== llamalend.constants.COINS.crv);
765
+
766
+ const tokenInfoCalls = [];
767
+ for (const token of tokens) {
768
+ llamalend.setContract(token, ERC20Abi);
769
+ const tokenMulticallContract = llamalend.contracts[token].multicallContract;
770
+ tokenInfoCalls.push(tokenMulticallContract.symbol(), tokenMulticallContract.decimals());
771
+ }
772
+ const tokenInfo = await llamalend.multicallProvider.all(tokenInfoCalls);
773
+ for (let i = 0; i < tokens.length; i++) {
774
+ llamalend.constants.DECIMALS[tokens[i]] = Number(tokenInfo[(i * 2) + 1]);
775
+ }
776
+
777
+ return tokens.map((token, i) => ({ token, symbol: tokenInfo[i * 2] as string, decimals: Number(tokenInfo[(i * 2) + 1]) }));
778
+ },
779
+ {
780
+ promise: true,
781
+ maxAge: 30 * 60 * 1000, // 30m
782
+ });
783
+
784
+ private vaultRewardsApr = async (useApi = true): Promise<IReward[]> => {
785
+ if(useApi) {
786
+ const response = await _getMarketsData(llamalend.constants.NETWORK_NAME);
787
+
788
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.addresses.vault.toLowerCase())
789
+
790
+ if(market) {
791
+ return market.gaugeRewards
792
+ } else {
793
+ throw new Error('Market not found in API')
794
+ }
795
+ } else {
796
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) return [];
797
+
798
+ // const isDisabledChain = [1313161554].includes(llamalend.chainId); // Disable Aurora
799
+ // if (useApi && !isDisabledChain) {
800
+ // const rewards = await _getRewardsFromApi();
801
+ // if (!rewards[this.addresses.gauge]) return [];
802
+ // return rewards[this.addresses.gauge].map((r) => ({ gaugeAddress: r.gaugeAddress, tokenAddress: r.tokenAddress, symbol: r.symbol, apy: r.apy }));
803
+ // }
804
+
805
+ const apy: IReward[] = [];
806
+ const rewardTokens = await this.vaultRewardTokens(false);
807
+ for (const rewardToken of rewardTokens) {
808
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].multicallContract;
809
+ const lpTokenContract = llamalend.contracts[this.addresses.vault].multicallContract;
810
+ const rewardContract = llamalend.contracts[this.addresses.gauge].multicallContract;
811
+
812
+ const totalLiquidityUSD = await this.vaultTotalLiquidity();
813
+ const rewardRate = await _getUsdRate(rewardToken.token);
814
+
815
+ const [rewardData, _stakedSupply, _totalSupply] = (await llamalend.multicallProvider.all([
816
+ rewardContract.reward_data(rewardToken.token),
817
+ gaugeContract.totalSupply(),
818
+ lpTokenContract.totalSupply(),
819
+ ]) as any[]);
820
+ const stakedSupplyBN = toBN(_stakedSupply as bigint);
821
+ const totalSupplyBN = toBN(_totalSupply as bigint);
822
+ const inflationBN = toBN(rewardData.rate, rewardToken.decimals);
823
+ const periodFinish = Number(llamalend.formatUnits(rewardData.period_finish, 0)) * 1000;
824
+ const baseApy = periodFinish > Date.now() ?
825
+ inflationBN.times(31536000).times(rewardRate).div(stakedSupplyBN).times(totalSupplyBN).div(Number(totalLiquidityUSD)) :
826
+ BN(0);
827
+
828
+ apy.push({
829
+ gaugeAddress: this.addresses.gauge,
830
+ tokenAddress: rewardToken.token,
831
+ symbol: rewardToken.symbol,
832
+ apy: baseApy.times(100).toNumber(),
833
+ });
834
+ }
835
+
836
+ return apy
837
+ }
838
+ }
839
+
840
+ private async vaultClaimableRewards(address = ""): Promise<{token: string, symbol: string, amount: string}[]> {
841
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
842
+ throw Error(`claimableRewards method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
843
+ }
844
+ address = address || llamalend.signerAddress;
845
+ if (!address) throw Error("Need to connect wallet or pass address into args");
846
+
847
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].contract;
848
+ const rewardTokens = await this.vaultRewardTokens();
849
+ const rewards = [];
850
+ for (const rewardToken of rewardTokens) {
851
+ const _amount = await gaugeContract.claimable_reward(address, rewardToken.token, llamalend.constantOptions);
852
+ rewards.push({
853
+ token: rewardToken.token,
854
+ symbol: rewardToken.symbol,
855
+ amount: llamalend.formatUnits(_amount, rewardToken.decimals),
856
+ });
857
+ }
858
+
859
+ return rewards
860
+ }
861
+
862
+ private async _vaultClaimRewards(estimateGas: boolean): Promise<string | TGas> {
863
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
864
+ throw Error(`claimRewards method doesn't exist for pool ${this.name} (id: ${this.name}). There is no gauge`);
865
+ }
866
+ const gaugeContract = llamalend.contracts[this.addresses.gauge].contract;
867
+ if (!("claim_rewards()" in gaugeContract)) throw Error (`${this.name} pool doesn't have such method`);
868
+ const gas = await gaugeContract.claim_rewards.estimateGas(llamalend.constantOptions);
869
+ if (estimateGas) return smartNumber(gas);
870
+
871
+ await llamalend.updateFeeData();
872
+ const gasLimit = _mulBy1_3(DIGas(gas));
873
+ return (await gaugeContract.claim_rewards({ ...llamalend.options, gasLimit })).hash;
874
+ }
875
+
876
+ private async vaultClaimRewardsEstimateGas(): Promise<TGas> {
877
+ return await this._vaultClaimRewards(true) as TGas;
878
+ }
879
+
880
+ private async vaultClaimRewards(): Promise<string> {
881
+ return await this._vaultClaimRewards(false) as string;
882
+ }
883
+
884
+ // ---------------- STATS ----------------
885
+
886
+ private statsParameters = memoize(async (): Promise<{
887
+ fee: string, // %
888
+ admin_fee: string, // %
889
+ liquidation_discount: string, // %
890
+ loan_discount: string, // %
891
+ base_price: string,
892
+ A: string,
893
+ }> => {
894
+ const llammaContract = llamalend.contracts[this.addresses.amm].multicallContract;
895
+ const controllerContract = llamalend.contracts[this.addresses.controller].multicallContract;
896
+
897
+ const calls = [
898
+ llammaContract.fee(),
899
+ llammaContract.admin_fee(),
900
+ controllerContract.liquidation_discount(),
901
+ controllerContract.loan_discount(),
902
+ llammaContract.get_base_price(),
903
+ llammaContract.A(),
904
+ ]
905
+
906
+ const [_fee, _admin_fee, _liquidation_discount, _loan_discount, _base_price, _A]: bigint[] = await llamalend.multicallProvider.all(calls) as bigint[];
907
+ const A = formatUnits(_A, 0)
908
+ const base_price = formatUnits(_base_price)
909
+ const [fee, admin_fee, liquidation_discount, loan_discount] = [_fee, _admin_fee, _liquidation_discount, _loan_discount]
910
+ .map((_x) => formatUnits(_x * BigInt(100)));
911
+
912
+ return { fee, admin_fee, liquidation_discount, loan_discount, base_price, A }
913
+ },
914
+ {
915
+ promise: true,
916
+ maxAge: 5 * 60 * 1000, // 5m
917
+ });
918
+
919
+ private _getRate = async (isGetter = true): Promise<bigint> => {
920
+ let _rate;
921
+ if(isGetter) {
922
+ _rate = cacheStats.get(cacheKey(this.addresses.amm, 'rate'));
923
+ } else {
924
+ _rate = await llamalend.contracts[this.addresses.amm].contract.rate(llamalend.constantOptions);
925
+ cacheStats.set(cacheKey(this.addresses.controller, 'rate'), _rate);
926
+ }
927
+ return _rate;
928
+ }
929
+
930
+ private _getFutureRate = async (_dReserves: bigint, _dDebt: bigint): Promise<bigint> => {
931
+ const mpContract = llamalend.contracts[this.addresses.monetary_policy].contract;
932
+ return await mpContract.future_rate(this.addresses.controller, _dReserves, _dDebt);
933
+ }
934
+
935
+ private async statsRates(isGetter = true, useAPI = false): Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}> {
936
+ if(useAPI) {
937
+ const response = await _getMarketsData(llamalend.constants.NETWORK_NAME);
938
+
939
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.addresses.vault.toLowerCase())
940
+
941
+ if(market) {
942
+ return {
943
+ borrowApr: (market.rates.borrowApr * 100).toString(),
944
+ lendApr: (market.rates.lendApr * 100).toString(),
945
+ borrowApy: (market.rates.borrowApy * 100).toString(),
946
+ lendApy: (market.rates.lendApy * 100).toString(),
947
+ }
948
+ } else {
949
+ throw new Error('Market not found in API')
950
+ }
951
+ } else {
952
+ const _rate = await this._getRate(isGetter);
953
+ const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
954
+ // borrowApy = e**(rate*365*86400) - 1
955
+ const borrowApy = String(((2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber()) - 1) * 100);
956
+ let lendApr = "0";
957
+ let lendApy = "0";
958
+ const debt = await this.statsTotalDebt(isGetter);
959
+ if (Number(debt) > 0) {
960
+ const { cap } = await this.statsCapAndAvailable(isGetter);
961
+ lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
962
+ // lendApy = (debt * e**(rate*365*86400) - debt) / cap
963
+ const debtInAYearBN = BN(debt).times(2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber());
964
+ lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
965
+ }
966
+
967
+ return { borrowApr, lendApr, borrowApy, lendApy }
968
+ }
969
+ }
970
+
971
+ private async statsFutureRates(dReserves: TAmount, dDebt: TAmount, useAPI = true): Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}> {
972
+ const _dReserves = parseUnits(dReserves, this.borrowed_token.decimals);
973
+ const _dDebt = parseUnits(dDebt, this.borrowed_token.decimals);
974
+ const _rate = await this._getFutureRate(_dReserves, _dDebt);
975
+ const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
976
+ // borrowApy = e**(rate*365*86400) - 1
977
+ const borrowApy = String(((2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber()) - 1) * 100);
978
+ let lendApr = "0";
979
+ let lendApy = "0";
980
+ const debt = Number(await this.statsTotalDebt()) + Number(dDebt);
981
+ if (Number(debt) > 0) {
982
+ const cap = Number((await this.statsCapAndAvailable(true, useAPI)).cap) + Number(dReserves);
983
+ lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
984
+ // lendApy = (debt * e**(rate*365*86400) - debt) / cap
985
+ const debtInAYearBN = BN(debt).times(2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber());
986
+ lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
987
+ }
988
+
989
+ return { borrowApr, lendApr, borrowApy, lendApy }
990
+ }
991
+
992
+ private async statsBalances(): Promise<[string, string]> {
993
+ const borrowedContract = llamalend.contracts[this.borrowed_token.address].multicallContract;
994
+ const collateralContract = llamalend.contracts[this.collateral_token.address].multicallContract;
995
+ const ammContract = llamalend.contracts[this.addresses.amm].multicallContract;
996
+ const calls = [
997
+ borrowedContract.balanceOf(this.addresses.amm),
998
+ collateralContract.balanceOf(this.addresses.amm),
999
+ ammContract.admin_fees_x(),
1000
+ ammContract.admin_fees_y(),
1001
+ ]
1002
+ const [_borrowedBalance, _collateralBalance, _borrowedAdminFees, _collateralAdminFees]: bigint[] = await llamalend.multicallProvider.all(calls);
1003
+
1004
+ return [
1005
+ formatUnits(_borrowedBalance - _borrowedAdminFees, this.borrowed_token.decimals),
1006
+ formatUnits(_collateralBalance - _collateralAdminFees, this.collateral_token.decimals),
1007
+ ];
1008
+ }
1009
+
1010
+ private statsBandsInfo = memoize(async (): Promise<{ activeBand: number, maxBand: number, minBand: number, liquidationBand: number | null }> => {
1011
+ const ammContract = llamalend.contracts[this.addresses.amm].multicallContract;
1012
+ const calls = [
1013
+ ammContract.active_band(),
1014
+ ammContract.max_band(),
1015
+ ammContract.min_band(),
1016
+ ]
1017
+
1018
+ const [activeBand, maxBand, minBand] = (await llamalend.multicallProvider.all(calls) as bigint[]).map((_b) => Number(_b));
1019
+ const { borrowed, collateral } = await this.statsBandBalances(activeBand);
1020
+ let liquidationBand = null;
1021
+ if (Number(borrowed) > 0 && Number(collateral) > 0) liquidationBand = activeBand;
1022
+ return { activeBand, maxBand, minBand, liquidationBand }
1023
+ },
1024
+ {
1025
+ promise: true,
1026
+ maxAge: 60 * 1000, // 1m
1027
+ });
1028
+
1029
+ private async statsBandBalances(n: number): Promise<{ borrowed: string, collateral: string }> {
1030
+ const ammContract = llamalend.contracts[this.addresses.amm].multicallContract;
1031
+ const calls = [];
1032
+ calls.push(ammContract.bands_x(n), ammContract.bands_y(n));
1033
+ const _balances: bigint[] = await llamalend.multicallProvider.all(calls);
1034
+
1035
+ // bands_x and bands_y always return amounts with 18 decimals
1036
+ return {
1037
+ borrowed: formatNumber(formatUnits(_balances[0]), this.borrowed_token.decimals),
1038
+ collateral: formatNumber(formatUnits(_balances[1]), this.collateral_token.decimals),
1039
+ }
1040
+ }
1041
+
1042
+ private async statsBandsBalances(): Promise<{ [index: number]: { borrowed: string, collateral: string } }> {
1043
+ const { maxBand, minBand } = await this.statsBandsInfo();
1044
+
1045
+ const ammContract = llamalend.contracts[this.addresses.amm].multicallContract;
1046
+ const calls = [];
1047
+ for (let i = minBand; i <= maxBand; i++) {
1048
+ calls.push(ammContract.bands_x(i), ammContract.bands_y(i));
1049
+ }
1050
+
1051
+ const _bands: bigint[] = await llamalend.multicallProvider.all(calls);
1052
+
1053
+ const bands: { [index: number]: { borrowed: string, collateral: string } } = {};
1054
+ for (let i = minBand; i <= maxBand; i++) {
1055
+ const _i = i - minBand
1056
+ // bands_x and bands_y always return amounts with 18 decimals
1057
+ bands[i] = {
1058
+ borrowed: formatNumber(formatUnits(_bands[2 * _i]), this.borrowed_token.decimals),
1059
+ collateral: formatNumber(formatUnits(_bands[(2 * _i) + 1]), this.collateral_token.decimals),
1060
+ }
1061
+ }
1062
+
1063
+ return bands
1064
+ }
1065
+
1066
+ private async statsTotalDebt(isGetter = true, useAPI = true): Promise<string> {
1067
+ if(useAPI) {
1068
+ const response = await _getMarketsData(llamalend.constants.NETWORK_NAME);
1069
+
1070
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.addresses.vault.toLowerCase())
1071
+
1072
+ if(market) {
1073
+ return market.borrowed.total.toString();
1074
+ } else {
1075
+ throw new Error('Market not found in API')
1076
+ }
1077
+ } else {
1078
+ let _debt;
1079
+ if(isGetter) {
1080
+ _debt = cacheStats.get(cacheKey(this.addresses.controller, 'total_debt'));
1081
+ } else {
1082
+ _debt = await llamalend.contracts[this.addresses.controller].contract.total_debt(llamalend.constantOptions);
1083
+ cacheStats.set(cacheKey(this.addresses.controller, 'total_debt'), _debt);
1084
+ }
1085
+
1086
+ return formatUnits(_debt, this.borrowed_token.decimals);
1087
+ }
1088
+ }
1089
+
1090
+ private statsAmmBalances = async (isGetter = true, useAPI = false): Promise<{ borrowed: string, collateral: string }> => {
1091
+ if(useAPI) {
1092
+ const response = await _getMarketsData(llamalend.constants.NETWORK_NAME);
1093
+
1094
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.addresses.vault.toLowerCase())
1095
+
1096
+ if(market) {
1097
+ return {
1098
+ borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
1099
+ collateral: market.ammBalances.ammBalanceCollateral.toString(),
1100
+ }
1101
+ } else {
1102
+ throw new Error('Market not found in API')
1103
+ }
1104
+ } else {
1105
+ const borrowedContract = llamalend.contracts[this.addresses.borrowed_token].multicallContract;
1106
+ const collateralContract = llamalend.contracts[this.addresses.collateral_token].multicallContract;
1107
+ const ammContract = llamalend.contracts[this.addresses.amm].multicallContract;
1108
+
1109
+ let _balance_x, _fee_x, _balance_y, _fee_y;
1110
+ if(isGetter) {
1111
+ [_balance_x, _fee_x, _balance_y, _fee_y] = [
1112
+ cacheStats.get(cacheKey(this.addresses.borrowed_token, 'balanceOf', this.addresses.amm)),
1113
+ cacheStats.get(cacheKey(this.addresses.amm, 'admin_fees_x')),
1114
+ cacheStats.get(cacheKey(this.addresses.collateral_token, 'balanceOf', this.addresses.amm)),
1115
+ cacheStats.get(cacheKey(this.addresses.amm, 'admin_fees_y')),
1116
+ ]
1117
+ } else {
1118
+ [_balance_x, _fee_x, _balance_y, _fee_y] = await llamalend.multicallProvider.all([
1119
+ borrowedContract.balanceOf(this.addresses.amm),
1120
+ ammContract.admin_fees_x(),
1121
+ collateralContract.balanceOf(this.addresses.amm),
1122
+ ammContract.admin_fees_y(),
1123
+ ]);
1124
+ cacheStats.set(cacheKey(this.addresses.borrowed_token, 'balanceOf', this.addresses.amm), _balance_x);
1125
+ cacheStats.set(cacheKey(this.addresses.amm, 'admin_fees_x'), _fee_x);
1126
+ cacheStats.set(cacheKey(this.addresses.collateral_token, 'balanceOf', this.addresses.amm), _balance_y);
1127
+ cacheStats.set(cacheKey(this.addresses.amm, 'admin_fees_y'), _fee_y);
1128
+ }
1129
+
1130
+ return {
1131
+ borrowed: toBN(_balance_x, this.borrowed_token.decimals).minus(toBN(_fee_x, this.borrowed_token.decimals)).toString(),
1132
+ collateral: toBN(_balance_y, this.collateral_token.decimals).minus(toBN(_fee_y, this.collateral_token.decimals)).toString(),
1133
+ }
1134
+ }
1135
+ }
1136
+
1137
+ private async statsCapAndAvailable(isGetter = true, useAPI = false): Promise<{ cap: string, available: string }> {
1138
+ if(useAPI) {
1139
+ const response = await _getMarketsData(llamalend.constants.NETWORK_NAME);
1140
+
1141
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.addresses.vault.toLowerCase())
1142
+
1143
+ if(market) {
1144
+ return {
1145
+ cap: market.totalSupplied.total.toString(),
1146
+ available: market.availableToBorrow.total.toString(),
1147
+ }
1148
+ } else {
1149
+ throw new Error('Market not found in API')
1150
+ }
1151
+ } else {
1152
+ const vaultContract = llamalend.contracts[this.addresses.vault].multicallContract;
1153
+ const borrowedContract = llamalend.contracts[this.addresses.borrowed_token].multicallContract;
1154
+
1155
+ let _cap, _available;
1156
+ if(isGetter) {
1157
+ _cap = cacheStats.get(cacheKey(this.addresses.vault, 'totalAssets', this.addresses.controller));
1158
+ _available = cacheStats.get(cacheKey(this.addresses.borrowed_token, 'balanceOf', this.addresses.controller));
1159
+ } else {
1160
+ [_cap, _available] =await llamalend.multicallProvider.all([
1161
+ vaultContract.totalAssets(this.addresses.controller),
1162
+ borrowedContract.balanceOf(this.addresses.controller),
1163
+ ]);
1164
+ cacheStats.set(cacheKey(this.addresses.vault, 'totalAssets', this.addresses.controller), _cap);
1165
+ cacheStats.set(cacheKey(this.addresses.borrowed_token, 'balanceOf', this.addresses.controller), _available);
1166
+ }
1167
+
1168
+ return {
1169
+ cap: llamalend.formatUnits(_cap, this.borrowed_token.decimals),
1170
+ available: llamalend.formatUnits(_available, this.borrowed_token.decimals),
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ // ---------------- PRICES ----------------
1176
+
1177
+ public A = memoize(async(): Promise<string> => {
1178
+ const _A = await llamalend.contracts[this.addresses.amm].contract.A(llamalend.constantOptions) as bigint;
1179
+ return formatUnits(_A, 0);
1180
+ },
1181
+ {
1182
+ promise: true,
1183
+ maxAge: 86400 * 1000, // 1d
1184
+ });
1185
+
1186
+ public basePrice = memoize(async(): Promise<string> => {
1187
+ const _price = await llamalend.contracts[this.addresses.amm].contract.get_base_price(llamalend.constantOptions) as bigint;
1188
+ return formatUnits(_price);
1189
+ },
1190
+ {
1191
+ promise: true,
1192
+ maxAge: 86400 * 1000, // 1d
1193
+ });
1194
+
1195
+ public oraclePrice = memoize(async (): Promise<string> => {
1196
+ const _price = await llamalend.contracts[this.addresses.amm].contract.price_oracle(llamalend.constantOptions) as bigint;
1197
+ return formatUnits(_price);
1198
+ },
1199
+ {
1200
+ promise: true,
1201
+ maxAge: 60 * 1000, // 1m
1202
+ });
1203
+
1204
+ public async oraclePriceBand(): Promise<number> {
1205
+ const oraclePriceBN = BN(await this.oraclePrice());
1206
+ const basePriceBN = BN(await this.basePrice());
1207
+ const A_BN = BN(await this.A());
1208
+ const multiplier = oraclePriceBN.lte(basePriceBN) ? A_BN.minus(1).div(A_BN) : A_BN.div(A_BN.minus(1));
1209
+ const term = oraclePriceBN.lte(basePriceBN) ? 1 : -1;
1210
+ const compareFunc = oraclePriceBN.lte(basePriceBN) ?
1211
+ (oraclePriceBN: BigNumber, currentTickPriceBN: BigNumber) => oraclePriceBN.lte(currentTickPriceBN) :
1212
+ (oraclePriceBN: BigNumber, currentTickPriceBN: BigNumber) => oraclePriceBN.gt(currentTickPriceBN);
1213
+
1214
+ let band = 0;
1215
+ let currentTickPriceBN = oraclePriceBN.lte(basePriceBN) ? basePriceBN.times(multiplier) : basePriceBN;
1216
+ while (compareFunc(oraclePriceBN, currentTickPriceBN)) {
1217
+ currentTickPriceBN = currentTickPriceBN.times(multiplier);
1218
+ band += term;
1219
+ }
1220
+
1221
+ return band;
1222
+ }
1223
+
1224
+ public async price(): Promise<string> {
1225
+ const _price = await llamalend.contracts[this.addresses.amm].contract.get_p(llamalend.constantOptions) as bigint;
1226
+ return formatUnits(_price);
1227
+ }
1228
+
1229
+ public async calcTickPrice(n: number): Promise<string> {
1230
+ const basePrice = await this.basePrice();
1231
+ const basePriceBN = BN(basePrice);
1232
+ const A_BN = BN(await this.A());
1233
+
1234
+ return _cutZeros(basePriceBN.times(A_BN.minus(1).div(A_BN).pow(n)).toFixed(18))
1235
+ }
1236
+
1237
+ public async calcBandPrices(n: number): Promise<[string, string]> {
1238
+ return [await this.calcTickPrice(n + 1), await this.calcTickPrice(n)]
1239
+ }
1240
+
1241
+ public async calcRangePct(range: number): Promise<string> {
1242
+ const A_BN = BN(await this.A());
1243
+ const startBN = BN(1);
1244
+ const endBN = A_BN.minus(1).div(A_BN).pow(range);
1245
+
1246
+ return startBN.minus(endBN).times(100).toFixed(6)
1247
+ }
1248
+
1249
+ // ---------------- WALLET BALANCES ----------------
1250
+
1251
+ private async walletBalances(address = ""): Promise<{ collateral: string, borrowed: string, vaultShares: string, gauge: string }> {
1252
+ if (this.addresses.gauge === llamalend.constants.ZERO_ADDRESS) {
1253
+ const [collateral, borrowed, vaultShares] =
1254
+ await getBalances([this.collateral_token.address, this.borrowed_token.address, this.addresses.vault], address);
1255
+ return { collateral, borrowed, vaultShares, gauge: "0" }
1256
+ } else {
1257
+ const [collateral, borrowed, vaultShares, gauge] =
1258
+ await getBalances([this.collateral_token.address, this.borrowed_token.address, this.addresses.vault, this.addresses.gauge], address);
1259
+ return { collateral, borrowed, vaultShares, gauge }
1260
+ }
1261
+ }
1262
+
1263
+ // ---------------- USER POSITION ----------------
1264
+
1265
+ public async userLoanExists(address = ""): Promise<boolean> {
1266
+ address = _getAddress(address);
1267
+ return await llamalend.contracts[this.addresses.controller].contract.loan_exists(address, llamalend.constantOptions);
1268
+ }
1269
+
1270
+ public _userState = memoize(async (address = ""): Promise<{ _collateral: bigint, _borrowed: bigint, _debt: bigint, _N: bigint }> => {
1271
+ address = _getAddress(address);
1272
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1273
+ const [_collateral, _borrowed, _debt, _N] = await contract.user_state(address, llamalend.constantOptions) as bigint[];
1274
+
1275
+ return { _collateral, _borrowed, _debt, _N }
1276
+ },
1277
+ {
1278
+ promise: true,
1279
+ maxAge: 10 * 1000, // 10s
1280
+ });
1281
+
1282
+ public async userState(address = ""): Promise<{ collateral: string, borrowed: string, debt: string, N: string }> {
1283
+ const { _collateral, _borrowed, _debt, _N } = await this._userState(address);
1284
+
1285
+ return {
1286
+ collateral: formatUnits(_collateral, this.collateral_token.decimals),
1287
+ borrowed: formatUnits(_borrowed, this.borrowed_token.decimals),
1288
+ debt: formatUnits(_debt, this.borrowed_token.decimals),
1289
+ N: formatUnits(_N, 0),
1290
+ };
1291
+ }
1292
+
1293
+ public async userHealth(full = true, address = ""): Promise<string> {
1294
+ address = _getAddress(address);
1295
+ let _health = await llamalend.contracts[this.addresses.controller].contract.health(address, full, llamalend.constantOptions) as bigint;
1296
+ _health = _health * BigInt(100);
1297
+
1298
+ return formatUnits(_health);
1299
+ }
1300
+
1301
+ private async _userBands(address: string): Promise<bigint[]> {
1302
+ address = _getAddress(address);
1303
+ const _bands = await llamalend.contracts[this.addresses.amm].contract.read_user_tick_numbers(address, llamalend.constantOptions) as bigint[];
1304
+
1305
+ return Array.from(_bands).reverse();
1306
+ }
1307
+
1308
+ public async userBands(address = ""): Promise<number[]> {
1309
+ return (await this._userBands(address)).map((_t) => Number(_t));
1310
+ }
1311
+
1312
+ public async userRange(address = ""): Promise<number> {
1313
+ const [n2, n1] = await this.userBands(address);
1314
+ if (n1 == n2) return 0;
1315
+ return n2 - n1 + 1;
1316
+ }
1317
+
1318
+ public async userPrices(address = ""): Promise<string[]> {
1319
+ address = _getAddress(address);
1320
+ const _prices = await llamalend.contracts[this.addresses.controller].contract.user_prices(address, llamalend.constantOptions) as bigint[];
1321
+
1322
+ return _prices.map((_p) => formatUnits(_p)).reverse();
1323
+ }
1324
+
1325
+ public async userLoss(userAddress = ""): Promise<{ deposited_collateral: string, current_collateral_estimation: string, loss: string, loss_pct: string }> {
1326
+ userAddress = _getAddress(userAddress);
1327
+ const [userCollateral, _current_collateral_estimation] = await Promise.all([
1328
+ _getUserCollateral(llamalend.constants.NETWORK_NAME, this.addresses.controller, userAddress),
1329
+ llamalend.contracts[this.addresses.amm].contract.get_y_up(userAddress),
1330
+ ]);
1331
+
1332
+ const deposited_collateral = userCollateral.total_deposit_precise;
1333
+
1334
+ const current_collateral_estimation = llamalend.formatUnits(_current_collateral_estimation, this.collateral_token.decimals);
1335
+ if (BN(deposited_collateral).lte(0)) {
1336
+ return {
1337
+ deposited_collateral,
1338
+ current_collateral_estimation,
1339
+ loss: "0.0",
1340
+ loss_pct: "0.0",
1341
+ };
1342
+ }
1343
+ const loss = BN(deposited_collateral).minus(current_collateral_estimation).toString()
1344
+ const loss_pct = BN(loss).div(deposited_collateral).times(100).toString();
1345
+
1346
+ return {
1347
+ deposited_collateral,
1348
+ current_collateral_estimation,
1349
+ loss,
1350
+ loss_pct,
1351
+ };
1352
+ }
1353
+
1354
+ public async userBandsBalances(address = ""): Promise<IDict<{ collateral: string, borrowed: string }>> {
1355
+ const [n2, n1] = await this.userBands(address);
1356
+ if (n1 == 0 && n2 == 0) return {};
1357
+
1358
+ address = _getAddress(address);
1359
+ const contract = llamalend.contracts[this.addresses.amm].contract;
1360
+ const [_borrowed, _collateral] = await contract.get_xy(address, llamalend.constantOptions) as [bigint[], bigint[]];
1361
+
1362
+ const res: IDict<{ borrowed: string, collateral: string }> = {};
1363
+ for (let i = n1; i <= n2; i++) {
1364
+ res[i] = {
1365
+ collateral: formatUnits(_collateral[i - n1], this.collateral_token.decimals),
1366
+ borrowed: formatUnits(_borrowed[i - n1], this.borrowed_token.decimals),
1367
+ };
1368
+ }
1369
+
1370
+ return res
1371
+ }
1372
+
1373
+ // ---------------- CREATE LOAN ----------------
1374
+
1375
+ private _checkRange(range: number): void {
1376
+ if (range < this.minBands) throw Error(`range must be >= ${this.minBands}`);
1377
+ if (range > this.maxBands) throw Error(`range must be <= ${this.maxBands}`);
1378
+ }
1379
+
1380
+ public async createLoanMaxRecv(collateral: number | string, range: number): Promise<string> {
1381
+ this._checkRange(range);
1382
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1383
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1384
+
1385
+ return formatUnits(await contract.max_borrowable(_collateral, range, 0, llamalend.constantOptions), this.borrowed_token.decimals);
1386
+ }
1387
+
1388
+ public createLoanMaxRecvAllRanges = memoize(async (collateral: number | string): Promise<{ [index: number]: string }> => {
1389
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1390
+
1391
+ const calls = [];
1392
+ for (let N = this.minBands; N <= this.maxBands; N++) {
1393
+ calls.push(llamalend.contracts[this.addresses.controller].multicallContract.max_borrowable(_collateral, N, 0));
1394
+ }
1395
+ const _amounts = await llamalend.multicallProvider.all(calls) as bigint[];
1396
+
1397
+ const res: { [index: number]: string } = {};
1398
+ for (let N = this.minBands; N <= this.maxBands; N++) {
1399
+ res[N] = formatUnits(_amounts[N - this.minBands], this.borrowed_token.decimals);
1400
+ }
1401
+
1402
+ return res;
1403
+ },
1404
+ {
1405
+ promise: true,
1406
+ maxAge: 5 * 60 * 1000, // 5m
1407
+ });
1408
+
1409
+ public async getMaxRange(collateral: number | string, debt: number | string): Promise<number> {
1410
+ const maxRecv = await this.createLoanMaxRecvAllRanges(collateral);
1411
+ for (let N = this.minBands; N <= this.maxBands; N++) {
1412
+ if (BN(debt).gt(BN(maxRecv[N]))) return N - 1;
1413
+ }
1414
+
1415
+ return this.maxBands;
1416
+ }
1417
+
1418
+ private async _calcN1(_collateral: bigint, _debt: bigint, range: number): Promise<bigint> {
1419
+ this._checkRange(range);
1420
+ return await llamalend.contracts[this.addresses.controller].contract.calculate_debt_n1(_collateral, _debt, range, llamalend.constantOptions);
1421
+ }
1422
+
1423
+ private async _calcN1AllRanges(_collateral: bigint, _debt: bigint, maxN: number): Promise<bigint[]> {
1424
+ const calls = [];
1425
+ for (let N = this.minBands; N <= maxN; N++) {
1426
+ calls.push(llamalend.contracts[this.addresses.controller].multicallContract.calculate_debt_n1(_collateral, _debt, N));
1427
+ }
1428
+ return await llamalend.multicallProvider.all(calls) as bigint[];
1429
+ }
1430
+
1431
+ private async _getPrices(_n2: bigint, _n1: bigint): Promise<string[]> {
1432
+ const contract = llamalend.contracts[this.addresses.amm].multicallContract;
1433
+ return (await llamalend.multicallProvider.all([
1434
+ contract.p_oracle_down(_n2),
1435
+ contract.p_oracle_up(_n1),
1436
+ ]) as bigint[]).map((_p) => formatUnits(_p));
1437
+ }
1438
+
1439
+ private async _calcPrices(_n2: bigint, _n1: bigint): Promise<[string, string]> {
1440
+ return [await this.calcTickPrice(Number(_n2) + 1), await this.calcTickPrice(Number(_n1))];
1441
+ }
1442
+
1443
+ private async _createLoanBands(collateral: number | string, debt: number | string, range: number): Promise<[bigint, bigint]> {
1444
+ const _n1 = await this._calcN1(parseUnits(collateral, this.collateral_token.decimals), parseUnits(debt, this.borrowed_token.decimals), range);
1445
+ const _n2 = _n1 + BigInt(range - 1);
1446
+
1447
+ return [_n2, _n1];
1448
+ }
1449
+
1450
+ private async _createLoanBandsAllRanges(collateral: number | string, debt: number | string): Promise<{ [index: number]: [bigint, bigint] }> {
1451
+ const maxN = await this.getMaxRange(collateral, debt);
1452
+ const _n1_arr = await this._calcN1AllRanges(parseUnits(collateral, this.collateral_token.decimals), parseUnits(debt, this.borrowed_token.decimals), maxN);
1453
+ const _n2_arr: bigint[] = [];
1454
+ for (let N = this.minBands; N <= maxN; N++) {
1455
+ _n2_arr.push(_n1_arr[N - this.minBands] + BigInt(N - 1));
1456
+ }
1457
+
1458
+ const res: { [index: number]: [bigint, bigint] } = {};
1459
+ for (let N = this.minBands; N <= maxN; N++) {
1460
+ res[N] = [_n2_arr[N - this.minBands], _n1_arr[N - this.minBands]];
1461
+ }
1462
+
1463
+ return res;
1464
+ }
1465
+
1466
+ public async createLoanBands(collateral: number | string, debt: number | string, range: number): Promise<[number, number]> {
1467
+ const [_n2, _n1] = await this._createLoanBands(collateral, debt, range);
1468
+
1469
+ return [Number(_n2), Number(_n1)];
1470
+ }
1471
+
1472
+ public async createLoanBandsAllRanges(collateral: number | string, debt: number | string): Promise<{ [index: number]: [number, number] | null }> {
1473
+ const _bandsAllRanges = await this._createLoanBandsAllRanges(collateral, debt);
1474
+
1475
+ const bandsAllRanges: { [index: number]: [number, number] | null } = {};
1476
+ for (let N = this.minBands; N <= this.maxBands; N++) {
1477
+ if (_bandsAllRanges[N]) {
1478
+ bandsAllRanges[N] = _bandsAllRanges[N].map(Number) as [number, number];
1479
+ } else {
1480
+ bandsAllRanges[N] = null
1481
+ }
1482
+ }
1483
+
1484
+ return bandsAllRanges;
1485
+ }
1486
+
1487
+ public async createLoanPrices(collateral: number | string, debt: number | string, range: number): Promise<string[]> {
1488
+ const [_n2, _n1] = await this._createLoanBands(collateral, debt, range);
1489
+
1490
+ return await this._getPrices(_n2, _n1);
1491
+ }
1492
+
1493
+ public async createLoanPricesAllRanges(collateral: number | string, debt: number | string): Promise<{ [index: number]: [string, string] | null }> {
1494
+ const _bandsAllRanges = await this._createLoanBandsAllRanges(collateral, debt);
1495
+
1496
+ const pricesAllRanges: { [index: number]: [string, string] | null } = {};
1497
+ for (let N = this.minBands; N <= this.maxBands; N++) {
1498
+ if (_bandsAllRanges[N]) {
1499
+ pricesAllRanges[N] = await this._calcPrices(..._bandsAllRanges[N]);
1500
+ } else {
1501
+ pricesAllRanges[N] = null
1502
+ }
1503
+ }
1504
+
1505
+ return pricesAllRanges;
1506
+ }
1507
+
1508
+ public async createLoanHealth(collateral: number | string, debt: number | string, range: number, full = true): Promise<string> {
1509
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1510
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
1511
+
1512
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1513
+ let _health = await contract.health_calculator(llamalend.constants.ZERO_ADDRESS, _collateral, _debt, full, range, llamalend.constantOptions) as bigint;
1514
+ _health = _health * BigInt(100);
1515
+
1516
+ return formatUnits(_health);
1517
+ }
1518
+
1519
+ public async createLoanIsApproved(collateral: number | string): Promise<boolean> {
1520
+ return await hasAllowance([this.collateral_token.address], [collateral], llamalend.signerAddress, this.addresses.controller);
1521
+ }
1522
+
1523
+ private async createLoanApproveEstimateGas (collateral: number | string): Promise<TGas> {
1524
+ return await ensureAllowanceEstimateGas([this.collateral_token.address], [collateral], this.addresses.controller);
1525
+ }
1526
+
1527
+ public async createLoanApprove(collateral: number | string): Promise<string[]> {
1528
+ return await ensureAllowance([this.collateral_token.address], [collateral], this.addresses.controller);
1529
+ }
1530
+
1531
+ private async _createLoan(collateral: number | string, debt: number | string, range: number, estimateGas: boolean): Promise<string | TGas> {
1532
+ if (await this.userLoanExists()) throw Error("Loan already created");
1533
+ this._checkRange(range);
1534
+
1535
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1536
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
1537
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1538
+ const gas = await contract.create_loan.estimateGas(_collateral, _debt, range, { ...llamalend.constantOptions });
1539
+ if (estimateGas) return smartNumber(gas);
1540
+
1541
+ await llamalend.updateFeeData();
1542
+ const gasLimit = _mulBy1_3(DIGas(gas));
1543
+ return (await contract.create_loan(_collateral, _debt, range, { ...llamalend.options, gasLimit })).hash
1544
+ }
1545
+
1546
+ public async createLoanEstimateGas(collateral: number | string, debt: number | string, range: number): Promise<TGas> {
1547
+ if (!(await this.createLoanIsApproved(collateral))) throw Error("Approval is needed for gas estimation");
1548
+ return await this._createLoan(collateral, debt, range, true) as TGas;
1549
+ }
1550
+
1551
+ public async createLoan(collateral: number | string, debt: number | string, range: number): Promise<string> {
1552
+ await this.createLoanApprove(collateral);
1553
+ return await this._createLoan(collateral, debt, range, false) as string;
1554
+ }
1555
+
1556
+ // ---------------- BORROW MORE ----------------
1557
+
1558
+ public async borrowMoreMaxRecv(collateralAmount: number | string): Promise<string> {
1559
+ const { _collateral: _currentCollateral, _debt: _currentDebt, _N } = await this._userState();
1560
+ const _collateral = _currentCollateral + parseUnits(collateralAmount, this.collateral_token.decimals);
1561
+
1562
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1563
+ const _debt: bigint = await contract.max_borrowable(_collateral, _N, _currentDebt, llamalend.constantOptions);
1564
+
1565
+ return formatUnits(_debt - _currentDebt, this.borrowed_token.decimals);
1566
+ }
1567
+
1568
+ private async _borrowMoreBands(collateral: number | string, debt: number | string): Promise<[bigint, bigint]> {
1569
+ const { _collateral: _currentCollateral, _debt: _currentDebt, _N } = await this._userState();
1570
+ if (_currentDebt === BigInt(0)) throw Error(`Loan for ${llamalend.signerAddress} does not exist`);
1571
+
1572
+ const _collateral = _currentCollateral + parseUnits(collateral, this.collateral_token.decimals);
1573
+ const _debt = _currentDebt + parseUnits(debt, this.borrowed_token.decimals);
1574
+
1575
+ const _n1 = await this._calcN1(_collateral, _debt, Number(_N));
1576
+ const _n2 = _n1 + _N - BigInt(1);
1577
+
1578
+ return [_n2, _n1];
1579
+ }
1580
+
1581
+ public async borrowMoreBands(collateral: number | string, debt: number | string): Promise<[number, number]> {
1582
+ const [_n2, _n1] = await this._borrowMoreBands(collateral, debt);
1583
+
1584
+ return [Number(_n2), Number(_n1)];
1585
+ }
1586
+
1587
+ public async borrowMorePrices(collateral: number | string, debt: number | string): Promise<string[]> {
1588
+ const [_n2, _n1] = await this._borrowMoreBands(collateral, debt);
1589
+
1590
+ return await this._getPrices(_n2, _n1);
1591
+ }
1592
+
1593
+ public async borrowMoreHealth(collateral: number | string, debt: number | string, full = true, address = ""): Promise<string> {
1594
+ address = _getAddress(address);
1595
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1596
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
1597
+
1598
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1599
+ let _health = await contract.health_calculator(address, _collateral, _debt, full, 0, llamalend.constantOptions) as bigint;
1600
+ _health = _health * BigInt(100);
1601
+
1602
+ return formatUnits(_health);
1603
+ }
1604
+
1605
+ public async borrowMoreIsApproved(collateral: number | string): Promise<boolean> {
1606
+ return await hasAllowance([this.addresses.collateral_token], [collateral], llamalend.signerAddress, this.addresses.controller);
1607
+ }
1608
+
1609
+ private async borrowMoreApproveEstimateGas (collateral: number | string): Promise<TGas> {
1610
+ return await ensureAllowanceEstimateGas([this.addresses.collateral_token], [collateral], this.addresses.controller);
1611
+ }
1612
+
1613
+ public async borrowMoreApprove(collateral: number | string): Promise<string[]> {
1614
+ return await ensureAllowance([this.addresses.collateral_token], [collateral], this.addresses.controller);
1615
+ }
1616
+
1617
+ private async _borrowMore(collateral: number | string, debt: number | string, estimateGas: boolean): Promise<string | TGas> {
1618
+ const { borrowed, debt: currentDebt } = await this.userState();
1619
+ if (Number(currentDebt) === 0) throw Error(`Loan for ${llamalend.signerAddress} does not exist`);
1620
+ if (Number(borrowed) > 0) throw Error(`User ${llamalend.signerAddress} is already in liquidation mode`);
1621
+
1622
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1623
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
1624
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1625
+ const gas = await contract.borrow_more.estimateGas(_collateral, _debt, { ...llamalend.constantOptions });
1626
+ if (estimateGas) return smartNumber(gas);
1627
+
1628
+ await llamalend.updateFeeData();
1629
+ const gasLimit = _mulBy1_3(DIGas(gas));
1630
+ return (await contract.borrow_more(_collateral, _debt, { ...llamalend.options, gasLimit })).hash
1631
+ }
1632
+
1633
+ public async borrowMoreEstimateGas(collateral: number | string, debt: number | string): Promise<TGas> {
1634
+ if (!(await this.borrowMoreIsApproved(collateral))) throw Error("Approval is needed for gas estimation");
1635
+ return await this._borrowMore(collateral, debt, true) as TGas;
1636
+ }
1637
+
1638
+ public async borrowMore(collateral: number | string, debt: number | string): Promise<string> {
1639
+ await this.borrowMoreApprove(collateral);
1640
+ return await this._borrowMore(collateral, debt, false) as string;
1641
+ }
1642
+
1643
+ // ---------------- ADD COLLATERAL ----------------
1644
+
1645
+ private async _addCollateralBands(collateral: number | string, address = ""): Promise<[bigint, bigint]> {
1646
+ address = _getAddress(address);
1647
+ const { _collateral: _currentCollateral, _debt: _currentDebt, _N } = await this._userState(address);
1648
+ if (_currentDebt === BigInt(0)) throw Error(`Loan for ${address} does not exist`);
1649
+
1650
+ const _collateral = _currentCollateral + parseUnits(collateral, this.collateral_token.decimals);
1651
+ const _n1 = await this._calcN1(_collateral, _currentDebt, Number(_N));
1652
+ const _n2 = _n1 + _N - BigInt(1);
1653
+
1654
+ return [_n2, _n1];
1655
+ }
1656
+
1657
+ public async addCollateralBands(collateral: number | string, address = ""): Promise<[number, number]> {
1658
+ const [_n2, _n1] = await this._addCollateralBands(collateral, address);
1659
+
1660
+ return [Number(_n2), Number(_n1)];
1661
+ }
1662
+
1663
+ public async addCollateralPrices(collateral: number | string, address = ""): Promise<string[]> {
1664
+ const [_n2, _n1] = await this._addCollateralBands(collateral, address);
1665
+
1666
+ return await this._getPrices(_n2, _n1);
1667
+ }
1668
+
1669
+ public async addCollateralHealth(collateral: number | string, full = true, address = ""): Promise<string> {
1670
+ address = _getAddress(address);
1671
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1672
+
1673
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1674
+ let _health = await contract.health_calculator(address, _collateral, 0, full, 0, llamalend.constantOptions) as bigint;
1675
+ _health = _health * BigInt(100);
1676
+
1677
+ return formatUnits(_health);
1678
+ }
1679
+
1680
+ public async addCollateralIsApproved(collateral: number | string): Promise<boolean> {
1681
+ return await hasAllowance([this.addresses.collateral_token], [collateral], llamalend.signerAddress, this.addresses.controller);
1682
+ }
1683
+
1684
+ private async addCollateralApproveEstimateGas (collateral: number | string): Promise<TGas> {
1685
+ return await ensureAllowanceEstimateGas([this.addresses.collateral_token], [collateral], this.addresses.controller);
1686
+ }
1687
+
1688
+ public async addCollateralApprove(collateral: number | string): Promise<string[]> {
1689
+ return await ensureAllowance([this.addresses.collateral_token], [collateral], this.addresses.controller);
1690
+ }
1691
+
1692
+ private async _addCollateral(collateral: number | string, address: string, estimateGas: boolean): Promise<string | TGas> {
1693
+ const { borrowed, debt: currentDebt } = await this.userState(address);
1694
+ if (Number(currentDebt) === 0) throw Error(`Loan for ${address} does not exist`);
1695
+ if (Number(borrowed) > 0) throw Error(`User ${address} is already in liquidation mode`);
1696
+
1697
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1698
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1699
+ const gas = await contract.add_collateral.estimateGas(_collateral, address, { ...llamalend.constantOptions });
1700
+ if (estimateGas) return smartNumber(gas);
1701
+
1702
+ await llamalend.updateFeeData();
1703
+ const gasLimit = _mulBy1_3(DIGas(gas));
1704
+ return (await contract.add_collateral(_collateral, address, { ...llamalend.options, gasLimit })).hash
1705
+ }
1706
+
1707
+ public async addCollateralEstimateGas(collateral: number | string, address = ""): Promise<TGas> {
1708
+ address = _getAddress(address);
1709
+ if (!(await this.addCollateralIsApproved(collateral))) throw Error("Approval is needed for gas estimation");
1710
+ return await this._addCollateral(collateral, address, true) as TGas;
1711
+ }
1712
+
1713
+ public async addCollateral(collateral: number | string, address = ""): Promise<string> {
1714
+ address = _getAddress(address);
1715
+ await this.addCollateralApprove(collateral);
1716
+ return await this._addCollateral(collateral, address, false) as string;
1717
+ }
1718
+
1719
+ // ---------------- REMOVE COLLATERAL ----------------
1720
+
1721
+ public async maxRemovable(): Promise<string> {
1722
+ const { _collateral: _currentCollateral, _debt: _currentDebt, _N } = await this._userState();
1723
+ const _requiredCollateral = await llamalend.contracts[this.addresses.controller].contract.min_collateral(_currentDebt, _N, llamalend.constantOptions)
1724
+
1725
+ return formatUnits(_currentCollateral - _requiredCollateral, this.collateral_token.decimals);
1726
+ }
1727
+
1728
+ private async _removeCollateralBands(collateral: number | string): Promise<[bigint, bigint]> {
1729
+ const { _collateral: _currentCollateral, _debt: _currentDebt, _N } = await this._userState();
1730
+ if (_currentDebt === BigInt(0)) throw Error(`Loan for ${llamalend.signerAddress} does not exist`);
1731
+
1732
+ const _collateral = _currentCollateral - parseUnits(collateral, this.collateral_token.decimals);
1733
+ const _n1 = await this._calcN1(_collateral, _currentDebt, Number(_N));
1734
+ const _n2 = _n1 + _N - BigInt(1);
1735
+
1736
+ return [_n2, _n1];
1737
+ }
1738
+
1739
+ public async removeCollateralBands(collateral: number | string): Promise<[number, number]> {
1740
+ const [_n2, _n1] = await this._removeCollateralBands(collateral);
1741
+
1742
+ return [Number(_n2), Number(_n1)];
1743
+ }
1744
+
1745
+ public async removeCollateralPrices(collateral: number | string): Promise<string[]> {
1746
+ const [_n2, _n1] = await this._removeCollateralBands(collateral);
1747
+
1748
+ return await this._getPrices(_n2, _n1);
1749
+ }
1750
+
1751
+ public async removeCollateralHealth(collateral: number | string, full = true, address = ""): Promise<string> {
1752
+ address = _getAddress(address);
1753
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals) * BigInt(-1);
1754
+
1755
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1756
+ let _health = await contract.health_calculator(address, _collateral, 0, full, 0, llamalend.constantOptions) as bigint;
1757
+ _health = _health * BigInt(100);
1758
+
1759
+ return formatUnits(_health);
1760
+ }
1761
+
1762
+ private async _removeCollateral(collateral: number | string, estimateGas: boolean): Promise<string | TGas> {
1763
+ const { borrowed, debt: currentDebt } = await this.userState();
1764
+ if (Number(currentDebt) === 0) throw Error(`Loan for ${llamalend.signerAddress} does not exist`);
1765
+ if (Number(borrowed) > 0) throw Error(`User ${llamalend.signerAddress} is already in liquidation mode`);
1766
+
1767
+ const _collateral = parseUnits(collateral, this.collateral_token.decimals);
1768
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1769
+ const gas = await contract.remove_collateral.estimateGas(_collateral, llamalend.constantOptions);
1770
+ if (estimateGas) return smartNumber(gas);
1771
+
1772
+ await llamalend.updateFeeData();
1773
+ const gasLimit = _mulBy1_3(DIGas(gas));
1774
+ return (await contract.remove_collateral(_collateral, { ...llamalend.options, gasLimit })).hash
1775
+ }
1776
+
1777
+ public async removeCollateralEstimateGas(collateral: number | string): Promise<TGas> {
1778
+ return await this._removeCollateral(collateral, true) as TGas;
1779
+ }
1780
+
1781
+ public async removeCollateral(collateral: number | string): Promise<string> {
1782
+ return await this._removeCollateral(collateral, false) as string;
1783
+ }
1784
+
1785
+ // ---------------- REPAY ----------------
1786
+
1787
+ private async _repayBands(debt: number | string, address: string): Promise<[bigint, bigint]> {
1788
+ const { _collateral: _currentCollateral, _borrowed, _debt: _currentDebt, _N } = await this._userState(address);
1789
+ if (_currentDebt === BigInt(0)) throw Error(`Loan for ${address} does not exist`);
1790
+ if (_borrowed > BigInt(0)) return await this._userBands(address) as [bigint, bigint];
1791
+
1792
+ const _debt = _currentDebt - parseUnits(debt, this.borrowed_token.decimals);
1793
+ const _n1 = await this._calcN1(_currentCollateral, _debt, Number(_N));
1794
+ const _n2 = _n1 + _N - BigInt(1);
1795
+
1796
+ return [_n2, _n1];
1797
+ }
1798
+
1799
+ public async repayBands(debt: number | string, address = ""): Promise<[number, number]> {
1800
+ const [_n2, _n1] = await this._repayBands(debt, address);
1801
+
1802
+ return [Number(_n2), Number(_n1)];
1803
+ }
1804
+
1805
+ public async repayPrices(debt: number | string, address = ""): Promise<string[]> {
1806
+ const [_n2, _n1] = await this._repayBands(debt, address);
1807
+
1808
+ return await this._getPrices(_n2, _n1);
1809
+ }
1810
+
1811
+ public async repayIsApproved(debt: number | string): Promise<boolean> {
1812
+ return await hasAllowance([this.borrowed_token.address], [debt], llamalend.signerAddress, this.addresses.controller);
1813
+ }
1814
+
1815
+ private async repayApproveEstimateGas (debt: number | string): Promise<TGas> {
1816
+ return await ensureAllowanceEstimateGas([this.borrowed_token.address], [debt], this.addresses.controller);
1817
+ }
1818
+
1819
+ public async repayApprove(debt: number | string): Promise<string[]> {
1820
+ return await ensureAllowance([this.borrowed_token.address], [debt], this.addresses.controller);
1821
+ }
1822
+
1823
+ public async repayHealth(debt: number | string, full = true, address = ""): Promise<string> {
1824
+ address = _getAddress(address);
1825
+ const _debt = parseUnits(debt) * BigInt(-1);
1826
+
1827
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1828
+ let _health = await contract.health_calculator(address, 0, _debt, full, 0, llamalend.constantOptions) as bigint;
1829
+ _health = _health * BigInt(100);
1830
+
1831
+ return formatUnits(_health);
1832
+ }
1833
+
1834
+ private async _repay(debt: number | string, address: string, estimateGas: boolean): Promise<string | TGas> {
1835
+ address = _getAddress(address);
1836
+ const { debt: currentDebt } = await this.userState(address);
1837
+ if (Number(currentDebt) === 0) throw Error(`Loan for ${address} does not exist`);
1838
+
1839
+ const _debt = parseUnits(debt);
1840
+ const contract = llamalend.contracts[this.addresses.controller].contract;
1841
+ const [_, n1] = await this.userBands(address);
1842
+ const { borrowed } = await this.userState(address);
1843
+ const n = (BN(borrowed).gt(0)) ? MAX_ACTIVE_BAND : n1 - 1; // In liquidation mode it doesn't matter if active band moves
1844
+ const gas = await contract.repay.estimateGas(_debt, address, n, llamalend.constantOptions);
1845
+ if (estimateGas) return smartNumber(gas);
1846
+
1847
+ await llamalend.updateFeeData();
1848
+ const gasLimit = _mulBy1_3(DIGas(gas));
1849
+ return (await contract.repay(_debt, address, n, { ...llamalend.options, gasLimit })).hash
1850
+ }
1851
+
1852
+ public async repayEstimateGas(debt: number | string, address = ""): Promise<TGas> {
1853
+ if (!(await this.repayIsApproved(debt))) throw Error("Approval is needed for gas estimation");
1854
+ return await this._repay(debt, address, true) as TGas;
1855
+ }
1856
+
1857
+ public async repay(debt: number | string, address = ""): Promise<string> {
1858
+ await this.repayApprove(debt);
1859
+ return await this._repay(debt, address, false) as string;
1860
+ }
1861
+
1862
+ // ---------------- FULL REPAY ----------------
1863
+
1864
+ private async _fullRepayAmount(address = ""): Promise<string> {
1865
+ address = _getAddress(address);
1866
+ const { debt } = await this.userState(address);
1867
+ return BN(debt).times(1.0001).toString();
1868
+ }
1869
+
1870
+ public async fullRepayIsApproved(address = ""): Promise<boolean> {
1871
+ address = _getAddress(address);
1872
+ const fullRepayAmount = await this._fullRepayAmount(address);
1873
+ return await this.repayIsApproved(fullRepayAmount);
1874
+ }
1875
+
1876
+ private async fullRepayApproveEstimateGas (address = ""): Promise<TGas> {
1877
+ address = _getAddress(address);
1878
+ const fullRepayAmount = await this._fullRepayAmount(address);
1879
+ return await this.repayApproveEstimateGas(fullRepayAmount);
1880
+ }
1881
+
1882
+ public async fullRepayApprove(address = ""): Promise<string[]> {
1883
+ address = _getAddress(address);
1884
+ const fullRepayAmount = await this._fullRepayAmount(address);
1885
+ return await this.repayApprove(fullRepayAmount);
1886
+ }
1887
+
1888
+ public async fullRepayEstimateGas(address = ""): Promise<TGas> {
1889
+ address = _getAddress(address);
1890
+ const fullRepayAmount = await this._fullRepayAmount(address);
1891
+ if (!(await this.repayIsApproved(fullRepayAmount))) throw Error("Approval is needed for gas estimation");
1892
+ return await this._repay(fullRepayAmount, address, true) as TGas;
1893
+ }
1894
+
1895
+ public async fullRepay(address = ""): Promise<string> {
1896
+ address = _getAddress(address);
1897
+ const fullRepayAmount = await this._fullRepayAmount(address);
1898
+ await this.repayApprove(fullRepayAmount);
1899
+ return await this._repay(fullRepayAmount, address, false) as string;
1900
+ }
1901
+
1902
+ // ---------------- SWAP ----------------
1903
+
1904
+ public async maxSwappable(i: number, j: number): Promise<string> {
1905
+ if (!(i === 0 && j === 1) && !(i === 1 && j === 0)) throw Error("Wrong index");
1906
+ const inDecimals = this.coinDecimals[i];
1907
+ const contract = llamalend.contracts[this.addresses.amm].contract;
1908
+ const [_inAmount, _outAmount] = await contract.get_dxdy(i, j, MAX_ALLOWANCE, llamalend.constantOptions) as bigint[];
1909
+ if (_outAmount === BigInt(0)) return "0";
1910
+
1911
+ return formatUnits(_inAmount, inDecimals)
1912
+ }
1913
+
1914
+ private async _swapExpected(i: number, j: number, _amount: bigint): Promise<bigint> {
1915
+ return await llamalend.contracts[this.addresses.amm].contract.get_dy(i, j, _amount, llamalend.constantOptions) as bigint;
1916
+ }
1917
+
1918
+ public async swapExpected(i: number, j: number, amount: number | string): Promise<string> {
1919
+ if (!(i === 0 && j === 1) && !(i === 1 && j === 0)) throw Error("Wrong index");
1920
+ const [inDecimals, outDecimals] = this.coinDecimals;
1921
+ const _amount = parseUnits(amount, inDecimals);
1922
+ const _expected = await this._swapExpected(i, j, _amount);
1923
+
1924
+ return formatUnits(_expected, outDecimals)
1925
+ }
1926
+
1927
+ public async swapRequired(i: number, j: number, outAmount: number | string): Promise<string> {
1928
+ if (!(i === 0 && j === 1) && !(i === 1 && j === 0)) throw Error("Wrong index");
1929
+ const [inDecimals, outDecimals] = this.coinDecimals;
1930
+ const _amount = parseUnits(outAmount, outDecimals);
1931
+ const _expected = await llamalend.contracts[this.addresses.amm].contract.get_dx(i, j, _amount, llamalend.constantOptions) as bigint;
1932
+
1933
+ return formatUnits(_expected, inDecimals)
1934
+ }
1935
+
1936
+ public async swapPriceImpact(i: number, j: number, amount: number | string): Promise<string> {
1937
+ if (!(i === 0 && j === 1) && !(i === 1 && j === 0)) throw Error("Wrong index");
1938
+ const [inDecimals, outDecimals] = this.coinDecimals;
1939
+ const _amount = parseUnits(amount, inDecimals);
1940
+ const _output = await this._swapExpected(i, j, _amount);
1941
+
1942
+ // Find k for which x * k = 10^15 or y * k = 10^15: k = max(10^15 / x, 10^15 / y)
1943
+ // For coins with d (decimals) <= 15: k = min(k, 0.2), and x0 = min(x * k, 10^d)
1944
+ // x0 = min(x * min(max(10^15 / x, 10^15 / y), 0.2), 10^d), if x0 == 0 then priceImpact = 0
1945
+ const target = BN(10 ** 15);
1946
+ const amountIntBN = BN(amount).times(10 ** inDecimals);
1947
+ const outputIntBN = toBN(_output, 0);
1948
+ const k = BigNumber.min(BigNumber.max(target.div(amountIntBN), target.div(outputIntBN)), 0.2);
1949
+ const smallAmountIntBN = BigNumber.min(amountIntBN.times(k), BN(10 ** inDecimals));
1950
+ if (smallAmountIntBN.toFixed(0) === '0') return '0';
1951
+
1952
+ const _smallAmount = fromBN(smallAmountIntBN.div(10 ** inDecimals), inDecimals);
1953
+ const _smallOutput = await this._swapExpected(i, j, _smallAmount);
1954
+
1955
+ const amountBN = BN(amount);
1956
+ const outputBN = toBN(_output, outDecimals);
1957
+ const smallAmountBN = toBN(_smallAmount, inDecimals);
1958
+ const smallOutputBN = toBN(_smallOutput, outDecimals);
1959
+
1960
+ const rateBN = outputBN.div(amountBN);
1961
+ const smallRateBN = smallOutputBN.div(smallAmountBN);
1962
+ if (rateBN.gt(smallRateBN)) return "0";
1963
+
1964
+ const slippageBN = BN(1).minus(rateBN.div(smallRateBN)).times(100);
1965
+
1966
+ return _cutZeros(slippageBN.toFixed(6));
1967
+ }
1968
+
1969
+ public async swapIsApproved(i: number, amount: number | string): Promise<boolean> {
1970
+ if (i !== 0 && i !== 1) throw Error("Wrong index");
1971
+
1972
+ return await hasAllowance([this.coinAddresses[i]], [amount], llamalend.signerAddress, this.addresses.amm);
1973
+ }
1974
+
1975
+ private async swapApproveEstimateGas (i: number, amount: number | string): Promise<TGas> {
1976
+ if (i !== 0 && i !== 1) throw Error("Wrong index");
1977
+
1978
+ return await ensureAllowanceEstimateGas([this.coinAddresses[i]], [amount], this.addresses.amm);
1979
+ }
1980
+
1981
+ public async swapApprove(i: number, amount: number | string): Promise<string[]> {
1982
+ if (i !== 0 && i !== 1) throw Error("Wrong index");
1983
+
1984
+ return await ensureAllowance([this.coinAddresses[i]], [amount], this.addresses.amm);
1985
+ }
1986
+
1987
+ private async _swap(i: number, j: number, amount: number | string, slippage: number, estimateGas: boolean): Promise<string | TGas> {
1988
+ if (!(i === 0 && j === 1) && !(i === 1 && j === 0)) throw Error("Wrong index");
1989
+
1990
+ const [inDecimals, outDecimals] = [this.coinDecimals[i], this.coinDecimals[j]];
1991
+ const _amount = parseUnits(amount, inDecimals);
1992
+ const _expected = await this._swapExpected(i, j, _amount);
1993
+ const minRecvAmountBN: BigNumber = toBN(_expected, outDecimals).times(100 - slippage).div(100);
1994
+ const _minRecvAmount = fromBN(minRecvAmountBN, outDecimals);
1995
+ const contract = llamalend.contracts[this.addresses.amm].contract;
1996
+ const gas = await contract.exchange.estimateGas(i, j, _amount, _minRecvAmount, llamalend.constantOptions);
1997
+ if (estimateGas) return smartNumber(gas);
1998
+
1999
+ await llamalend.updateFeeData();
2000
+ const gasLimit = _mulBy1_3(DIGas(gas));
2001
+ return (await contract.exchange(i, j, _amount, _minRecvAmount, { ...llamalend.options, gasLimit })).hash
2002
+ }
2003
+
2004
+ public async swapEstimateGas(i: number, j: number, amount: number | string, slippage = 0.1): Promise<TGas> {
2005
+ if (!(await this.swapIsApproved(i, amount))) throw Error("Approval is needed for gas estimation");
2006
+ return await this._swap(i, j, amount, slippage, true) as TGas;
2007
+ }
2008
+
2009
+ public async swap(i: number, j: number, amount: number | string, slippage = 0.1): Promise<string> {
2010
+ await this.swapApprove(i, amount);
2011
+ return await this._swap(i, j, amount, slippage, false) as string;
2012
+ }
2013
+
2014
+ // ---------------- LIQUIDATE ----------------
2015
+
2016
+ public async tokensToLiquidate(address = ""): Promise<string> {
2017
+ address = _getAddress(address);
2018
+ const _tokens = await llamalend.contracts[this.addresses.controller].contract.tokens_to_liquidate(address, llamalend.constantOptions) as bigint;
2019
+
2020
+ return formatUnits(_tokens, this.borrowed_token.decimals)
2021
+ }
2022
+
2023
+ public async liquidateIsApproved(address = ""): Promise<boolean> {
2024
+ const tokensToLiquidate = await this.tokensToLiquidate(address);
2025
+ return await hasAllowance([this.addresses.borrowed_token], [tokensToLiquidate], llamalend.signerAddress, this.addresses.controller);
2026
+ }
2027
+
2028
+ private async liquidateApproveEstimateGas (address = ""): Promise<TGas> {
2029
+ const tokensToLiquidate = await this.tokensToLiquidate(address);
2030
+ return await ensureAllowanceEstimateGas([this.addresses.borrowed_token], [tokensToLiquidate], this.addresses.controller);
2031
+ }
2032
+
2033
+ public async liquidateApprove(address = ""): Promise<string[]> {
2034
+ const tokensToLiquidate = await this.tokensToLiquidate(address);
2035
+ return await ensureAllowance([this.addresses.borrowed_token], [tokensToLiquidate], this.addresses.controller);
2036
+ }
2037
+
2038
+ private async _liquidate(address: string, slippage: number, estimateGas: boolean): Promise<string | TGas> {
2039
+ const { borrowed, debt: currentDebt } = await this.userState(address);
2040
+ if (slippage <= 0) throw Error("Slippage must be > 0");
2041
+ if (slippage > 100) throw Error("Slippage must be <= 100");
2042
+ if (Number(currentDebt) === 0) throw Error(`Loan for ${address} does not exist`);
2043
+ if (Number(borrowed) === 0) throw Error(`User ${address} is not in liquidation mode`);
2044
+
2045
+ const minAmountBN: BigNumber = BN(borrowed).times(100 - slippage).div(100);
2046
+ const _minAmount = fromBN(minAmountBN);
2047
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2048
+ const gas = (await contract.liquidate.estimateGas(address, _minAmount, llamalend.constantOptions))
2049
+ if (estimateGas) return smartNumber(gas);
2050
+
2051
+ await llamalend.updateFeeData();
2052
+ const gasLimit = _mulBy1_3(DIGas(gas));
2053
+ return (await contract.liquidate(address, _minAmount, { ...llamalend.options, gasLimit })).hash
2054
+ }
2055
+
2056
+ public async liquidateEstimateGas(address: string, slippage = 0.1): Promise<TGas> {
2057
+ if (!(await this.liquidateIsApproved(address))) throw Error("Approval is needed for gas estimation");
2058
+ return await this._liquidate(address, slippage, true) as TGas;
2059
+ }
2060
+
2061
+ public async liquidate(address: string, slippage = 0.1): Promise<string> {
2062
+ await this.liquidateApprove(address);
2063
+ return await this._liquidate(address, slippage, false) as string;
2064
+ }
2065
+
2066
+ // ---------------- SELF-LIQUIDATE ----------------
2067
+
2068
+ public async selfLiquidateIsApproved(): Promise<boolean> {
2069
+ return await this.liquidateIsApproved()
2070
+ }
2071
+
2072
+ private async selfLiquidateApproveEstimateGas (): Promise<TGas> {
2073
+ return this.liquidateApproveEstimateGas()
2074
+ }
2075
+
2076
+ public async selfLiquidateApprove(): Promise<string[]> {
2077
+ return await this.liquidateApprove()
2078
+ }
2079
+
2080
+ public async selfLiquidateEstimateGas(slippage = 0.1): Promise<TGas> {
2081
+ if (!(await this.selfLiquidateIsApproved())) throw Error("Approval is needed for gas estimation");
2082
+ return await this._liquidate(llamalend.signerAddress, slippage, true) as TGas;
2083
+ }
2084
+
2085
+ public async selfLiquidate(slippage = 0.1): Promise<string> {
2086
+ await this.selfLiquidateApprove();
2087
+ return await this._liquidate(llamalend.signerAddress, slippage, false) as string;
2088
+ }
2089
+
2090
+ // ---------------- LEVERAGE CREATE LOAN ----------------
2091
+
2092
+ private hasLeverage = (): boolean => {
2093
+ return llamalend.constants.ALIASES.leverage_zap !== llamalend.constants.ZERO_ADDRESS &&
2094
+ this._getMarketId() >= Number(llamalend.constants.ALIASES["leverage_markets_start_id"]);
2095
+ }
2096
+
2097
+ private _checkLeverageZap(): void {
2098
+ if (!this.hasLeverage()) {
2099
+ throw Error("This market does not support leverage");
2100
+ }
2101
+ }
2102
+
2103
+ private async _get_k_effective_BN(N: number): Promise<BigNumber> {
2104
+ // d_k_effective: uint256 = (1 - loan_discount) * sqrt((A-1)/A) / N
2105
+ // k_effective = d_k_effective * sum_{0..N-1}(((A-1) / A)**k)
2106
+ const { loan_discount, A } = await this.statsParameters();
2107
+ const A_BN = BN(A);
2108
+ const A_ratio_BN = A_BN.minus(1).div(A_BN);
2109
+
2110
+ const d_k_effective_BN = BN(100).minus(loan_discount).div(100).times(A_ratio_BN.sqrt()).div(N);
2111
+ let S = BN(0);
2112
+ for (let n = 0; n < N; n++) {
2113
+ S = S.plus(A_ratio_BN.pow(n))
2114
+ }
2115
+
2116
+ return d_k_effective_BN.times(S);
2117
+ }
2118
+
2119
+ private async maxLeverage(N: number): Promise<string> {
2120
+ // max_leverage = 1 / (k_effective - 1)
2121
+ const k_effective_BN = await this._get_k_effective_BN(N);
2122
+
2123
+ return BN(1).div(BN(1).minus(k_effective_BN)).toString()
2124
+ }
2125
+
2126
+ private async leverageCreateLoanMaxRecv(userCollateral: TAmount, userBorrowed: TAmount, range: number):
2127
+ Promise<{
2128
+ maxDebt: string,
2129
+ maxTotalCollateral: string,
2130
+ userCollateral: string,
2131
+ collateralFromUserBorrowed: string,
2132
+ collateralFromMaxDebt: string,
2133
+ maxLeverage: string,
2134
+ avgPrice: string,
2135
+ }> {
2136
+ // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg)
2137
+ this._checkLeverageZap();
2138
+ if (range > 0) this._checkRange(range);
2139
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2140
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2141
+
2142
+ const oraclePriceBand = await this.oraclePriceBand();
2143
+ let pAvgBN = BN(await this.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
2144
+ let maxBorrowablePrevBN = BN(0);
2145
+ let maxBorrowableBN = BN(0);
2146
+ let _userEffectiveCollateral = BigInt(0);
2147
+ let _maxLeverageCollateral = BigInt(0);
2148
+
2149
+ const contract = llamalend.contracts[llamalend.constants.ALIASES.leverage_zap].contract;
2150
+ for (let i = 0; i < 5; i++) {
2151
+ maxBorrowablePrevBN = maxBorrowableBN;
2152
+ _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals);
2153
+ let _maxBorrowable = await contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, range, fromBN(pAvgBN));
2154
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000)
2155
+ if (_maxBorrowable === BigInt(0)) break;
2156
+ maxBorrowableBN = toBN(_maxBorrowable, this.borrowed_token.decimals);
2157
+
2158
+ if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) {
2159
+ maxBorrowableBN = maxBorrowablePrevBN;
2160
+ break;
2161
+ }
2162
+
2163
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
2164
+ const _maxAdditionalCollateral = BigInt(await _getExpectedOdos(
2165
+ this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable + _userBorrowed, this.addresses.amm));
2166
+ pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.collateral_token.decimals));
2167
+ _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals);
2168
+ }
2169
+
2170
+ const userEffectiveCollateralBN = maxBorrowableBN.gt(0) ? toBN(_userEffectiveCollateral, this.collateral_token.decimals) : BN(0);
2171
+ const maxLeverageCollateralBN = toBN(_maxLeverageCollateral, this.collateral_token.decimals);
2172
+
2173
+ return {
2174
+ maxDebt: formatNumber(maxBorrowableBN.toString(), this.borrowed_token.decimals),
2175
+ maxTotalCollateral: formatNumber(maxLeverageCollateralBN.plus(userEffectiveCollateralBN).toString(), this.collateral_token.decimals),
2176
+ userCollateral: formatNumber(userCollateral, this.collateral_token.decimals),
2177
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.collateral_token.decimals),
2178
+ collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN.toString(), this.collateral_token.decimals),
2179
+ maxLeverage: maxLeverageCollateralBN.plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(),
2180
+ avgPrice: pAvgBN.toString(),
2181
+ };
2182
+ }
2183
+
2184
+ private leverageCreateLoanMaxRecvAllRanges = memoize(async (userCollateral: TAmount, userBorrowed: TAmount):
2185
+ Promise<IDict<{
2186
+ maxDebt: string,
2187
+ maxTotalCollateral: string,
2188
+ userCollateral: string,
2189
+ collateralFromUserBorrowed: string,
2190
+ collateralFromMaxDebt: string,
2191
+ maxLeverage: string,
2192
+ avgPrice: string,
2193
+ }>> => {
2194
+ this._checkLeverageZap();
2195
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2196
+ const contract = llamalend.contracts[llamalend.constants.ALIASES.leverage_zap].multicallContract;
2197
+
2198
+ const oraclePriceBand = await this.oraclePriceBand();
2199
+ const pAvgApproxBN = BN(await this.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
2200
+ let pAvgBN: BigNumber | null = null;
2201
+ const arrLength = this.maxBands - this.minBands + 1;
2202
+ let maxLeverageCollateralBN: BigNumber[] = new Array(arrLength).fill(BN(0));
2203
+ let _maxLeverageCollateral: bigint[] = new Array(arrLength).fill(BigInt(0));
2204
+ let maxBorrowablePrevBN: BigNumber[] = new Array(arrLength).fill(BN(0));
2205
+ let maxBorrowableBN: BigNumber[] = new Array(arrLength).fill(BN(0));
2206
+ let _maxBorrowable: bigint[] = new Array(arrLength).fill(BigInt(0));
2207
+
2208
+ for (let i = 0; i < 5; i++) {
2209
+ const pBN = pAvgBN ?? pAvgApproxBN;
2210
+ maxBorrowablePrevBN = maxBorrowableBN;
2211
+ const _userEffectiveCollateral: bigint = _userCollateral + fromBN(BN(userBorrowed).div(pBN), this.collateral_token.decimals);
2212
+ const calls = [];
2213
+ for (let N = this.minBands; N <= this.maxBands; N++) {
2214
+ const j = N - this.minBands;
2215
+ calls.push(contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral[j], N, fromBN(pBN)));
2216
+ }
2217
+ _maxBorrowable = (await llamalend.multicallProvider.all(calls) as bigint[]).map((_mb) => _mb * BigInt(998) / BigInt(1000));
2218
+ maxBorrowableBN = _maxBorrowable.map((_mb) => toBN(_mb, this.borrowed_token.decimals));
2219
+
2220
+ const deltaBN = maxBorrowableBN.map((mb, l) => mb.minus(maxBorrowablePrevBN[l]).abs().div(mb));
2221
+ if (BigNumber.max(...deltaBN).lt(0.0005)) {
2222
+ maxBorrowableBN = maxBorrowablePrevBN;
2223
+ break;
2224
+ }
2225
+
2226
+ if (pAvgBN === null){
2227
+ const _y = BigInt(await _getExpectedOdos(this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable[0], this.addresses.amm));
2228
+ const yBN = toBN(_y, this.collateral_token.decimals);
2229
+ pAvgBN = maxBorrowableBN[0].div(yBN);
2230
+ }
2231
+
2232
+ maxLeverageCollateralBN = maxBorrowableBN.map((mb) => mb.div(pAvgBN as BigNumber));
2233
+ _maxLeverageCollateral = maxLeverageCollateralBN.map((mlc) => fromBN(mlc, this.collateral_token.decimals));
2234
+ }
2235
+
2236
+ const userEffectiveCollateralBN = BN(userCollateral).plus(BN(userBorrowed).div(pAvgBN as BigNumber));
2237
+
2238
+ const res: IDict<{
2239
+ maxDebt: string,
2240
+ maxTotalCollateral: string,
2241
+ userCollateral: string,
2242
+ collateralFromUserBorrowed: string,
2243
+ collateralFromMaxDebt: string,
2244
+ maxLeverage: string,
2245
+ avgPrice: string,
2246
+ }> = {};
2247
+ for (let N = this.minBands; N <= this.maxBands; N++) {
2248
+ const j = N - this.minBands;
2249
+ res[N] = {
2250
+ maxDebt: formatNumber(maxBorrowableBN[j].toString(), this.borrowed_token.decimals),
2251
+ maxTotalCollateral: formatNumber(maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).toString(), this.collateral_token.decimals),
2252
+ userCollateral: formatNumber(userCollateral, this.collateral_token.decimals),
2253
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN as BigNumber).toString(), this.collateral_token.decimals),
2254
+ collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN[j].toString(), this.collateral_token.decimals),
2255
+ maxLeverage: maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(),
2256
+ avgPrice: (pAvgBN as BigNumber).toString(),
2257
+ };
2258
+ }
2259
+
2260
+ return res;
2261
+ },
2262
+ {
2263
+ promise: true,
2264
+ maxAge: 60 * 1000, // 1m
2265
+ });
2266
+
2267
+ private _setSwapDataToCache = async (inputCoinAddress: string, outputCoinAddress: string, _amount: bigint, slippage: number) => {
2268
+ let swapData = await _getQuoteOdos(inputCoinAddress, outputCoinAddress, _amount, this.addresses.amm, true, slippage);
2269
+ while (swapData.pathId == null) {
2270
+ swapData = await _getQuoteOdos(inputCoinAddress, outputCoinAddress, _amount, this.addresses.amm, true, slippage);
2271
+ }
2272
+ const key = `${inputCoinAddress}-${_amount}`;
2273
+ this.swapDataCache[key] = { ...swapData, slippage };
2274
+ }
2275
+
2276
+ private _getSwapDataFromCache = (inputCoinAddress: string, _amount: bigint): IQuoteOdos => {
2277
+ const key = `${inputCoinAddress}-${_amount}`;
2278
+ if (!(key in this.swapDataCache)) throw Error(
2279
+ "You must call corresponding `expected` method first " +
2280
+ "(leverage.createLoanExpectedCollateral, leverage.borrowMoreExpectedCollateral or leverage.repayExpectedBorrowed)"
2281
+ );
2282
+
2283
+ return this.swapDataCache[key]
2284
+ }
2285
+
2286
+ private _leverageExpectedCollateral = async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, user?: string):
2287
+ Promise<{ _futureStateCollateral: bigint, _totalCollateral: bigint, _userCollateral: bigint,
2288
+ _collateralFromUserBorrowed: bigint, _collateralFromDebt: bigint, avgPrice: string }> => {
2289
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2290
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2291
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2292
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
2293
+ const _additionalCollateral = BigInt(this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed).outAmounts[0]);
2294
+ const _collateralFromDebt = _debt * BigInt(10**18) / (_debt + _userBorrowed) * _additionalCollateral / BigInt(10**18);
2295
+ const _collateralFromUserBorrowed = _additionalCollateral - _collateralFromDebt;
2296
+ let _stateCollateral = BigInt(0);
2297
+ if (user) {
2298
+ const { _collateral, _borrowed } = await this._userState(user);
2299
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
2300
+ _stateCollateral = _collateral;
2301
+ }
2302
+ const _totalCollateral = _userCollateral + _additionalCollateral;
2303
+ const _futureStateCollateral = _stateCollateral + _totalCollateral;
2304
+ const avgPrice = toBN(_debt + _userBorrowed, this.borrowed_token.decimals).div(toBN(_additionalCollateral, this.collateral_token.decimals)).toString();
2305
+
2306
+ return { _futureStateCollateral, _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice };
2307
+ };
2308
+
2309
+ private async leverageCreateLoanExpectedCollateral(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage = 0.1):
2310
+ Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, leverage: string, avgPrice: string }> {
2311
+ this._checkLeverageZap();
2312
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2313
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2314
+ await this._setSwapDataToCache(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed, slippage);
2315
+ const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } =
2316
+ await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt);
2317
+ return {
2318
+ totalCollateral: formatUnits(_totalCollateral, this.collateral_token.decimals),
2319
+ userCollateral: formatUnits(_userCollateral, this.collateral_token.decimals),
2320
+ collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.collateral_token.decimals),
2321
+ collateralFromDebt: formatUnits(_collateralFromDebt, this.collateral_token.decimals),
2322
+ leverage: toBN(_collateralFromDebt + _userCollateral + _collateralFromUserBorrowed, this.collateral_token.decimals)
2323
+ .div(toBN(_userCollateral + _collateralFromUserBorrowed, this.collateral_token.decimals)).toString(),
2324
+ avgPrice,
2325
+ }
2326
+ }
2327
+
2328
+ private async leverageCreateLoanPriceImpact(userBorrowed: TAmount, debt: TAmount): Promise<string> {
2329
+ this._checkLeverageZap();
2330
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2331
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2332
+ return this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed).priceImpact.toString();
2333
+ }
2334
+
2335
+ private async leverageCreateLoanMaxRange(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise<number> {
2336
+ this._checkLeverageZap();
2337
+ const maxRecv = await this.leverageCreateLoanMaxRecvAllRanges(userCollateral, userBorrowed);
2338
+ for (let N = this.minBands; N <= this.maxBands; N++) {
2339
+ if (BN(debt).gt(maxRecv[N].maxDebt)) return N - 1;
2340
+ }
2341
+
2342
+ return this.maxBands;
2343
+ }
2344
+
2345
+ private _leverageCalcN1 = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, user?: string): Promise<bigint> => {
2346
+ if (range > 0) this._checkRange(range);
2347
+ let _stateDebt = BigInt(0);
2348
+ if (user) {
2349
+ const { _debt, _borrowed, _N } = await this._userState(user);
2350
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
2351
+ _stateDebt = _debt;
2352
+ if (range < 0) range = Number(llamalend.formatUnits(_N, 0));
2353
+ }
2354
+ const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt, user);
2355
+ const _debt = _stateDebt + parseUnits(debt, this.borrowed_token.decimals);
2356
+ return await llamalend.contracts[this.addresses.controller].contract.calculate_debt_n1(_futureStateCollateral, _debt, range, llamalend.constantOptions);
2357
+ },
2358
+ {
2359
+ promise: true,
2360
+ maxAge: 60 * 1000, // 1m
2361
+ });
2362
+
2363
+ private _leverageCalcN1AllRanges = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, maxN: number): Promise<bigint[]> => {
2364
+ const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt);
2365
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2366
+ const calls = [];
2367
+ for (let N = this.minBands; N <= maxN; N++) {
2368
+ calls.push(llamalend.contracts[this.addresses.controller].multicallContract.calculate_debt_n1(_futureStateCollateral, _debt, N));
2369
+ }
2370
+ return await llamalend.multicallProvider.all(calls) as bigint[];
2371
+ },
2372
+ {
2373
+ promise: true,
2374
+ maxAge: 60 * 1000, // 1m
2375
+ });
2376
+
2377
+ private async _leverageBands(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, user?: string): Promise<[bigint, bigint]> {
2378
+ const _n1 = await this._leverageCalcN1(userCollateral, userBorrowed, debt, range, user);
2379
+ if (range < 0) {
2380
+ const { N } = await this.userState(user);
2381
+ range = Number(N);
2382
+ }
2383
+ const _n2 = _n1 + BigInt(range - 1);
2384
+
2385
+ return [_n2, _n1];
2386
+ }
2387
+
2388
+ private async _leverageCreateLoanBandsAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise<IDict<[bigint, bigint]>> {
2389
+ const maxN = await this.leverageCreateLoanMaxRange(userCollateral, userBorrowed, debt);
2390
+ const _n1_arr = await this._leverageCalcN1AllRanges(userCollateral, userBorrowed, debt, maxN);
2391
+ const _n2_arr: bigint[] = [];
2392
+ for (let N = this.minBands; N <= maxN; N++) {
2393
+ _n2_arr.push(_n1_arr[N - this.minBands] + BigInt(N - 1));
2394
+ }
2395
+
2396
+ const _bands: IDict<[bigint, bigint]> = {};
2397
+ for (let N = this.minBands; N <= maxN; N++) {
2398
+ _bands[N] = [_n2_arr[N - this.minBands], _n1_arr[N - this.minBands]];
2399
+ }
2400
+
2401
+ return _bands;
2402
+ }
2403
+
2404
+ private async leverageCreateLoanBands(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number): Promise<[number, number]> {
2405
+ this._checkLeverageZap();
2406
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, range);
2407
+
2408
+ return [Number(_n2), Number(_n1)];
2409
+ }
2410
+
2411
+ private async leverageCreateLoanBandsAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise<IDict<[number, number] | null>> {
2412
+ this._checkLeverageZap();
2413
+ const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt);
2414
+
2415
+ const bands: { [index: number]: [number, number] | null } = {};
2416
+ for (let N = this.minBands; N <= this.maxBands; N++) {
2417
+ if (_bands[N]) {
2418
+ bands[N] = _bands[N].map(Number) as [number, number];
2419
+ } else {
2420
+ bands[N] = null
2421
+ }
2422
+ }
2423
+
2424
+ return bands;
2425
+ }
2426
+
2427
+ private async leverageCreateLoanPrices(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number): Promise<string[]> {
2428
+ this._checkLeverageZap();
2429
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, range);
2430
+
2431
+ return await this._getPrices(_n2, _n1);
2432
+ }
2433
+
2434
+ private async leverageCreateLoanPricesAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise<IDict<[string, string] | null>> {
2435
+ this._checkLeverageZap();
2436
+ const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt);
2437
+
2438
+ const prices: { [index: number]: [string, string] | null } = {};
2439
+ for (let N = this.minBands; N <= this.maxBands; N++) {
2440
+ if (_bands[N]) {
2441
+ prices[N] = await this._calcPrices(..._bands[N]);
2442
+ } else {
2443
+ prices[N] = null
2444
+ }
2445
+ }
2446
+
2447
+ return prices;
2448
+ }
2449
+
2450
+ private async _leverageHealth(
2451
+ userCollateral: TAmount,
2452
+ userBorrowed: TAmount,
2453
+ dDebt: TAmount,
2454
+ range: number,
2455
+ full: boolean,
2456
+ user = llamalend.constants.ZERO_ADDRESS
2457
+ ): Promise<string> {
2458
+ if (range > 0) this._checkRange(range);
2459
+ const { _totalCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, user);
2460
+ const { _borrowed, _N } = await this._userState(user);
2461
+ if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`);
2462
+ if (range < 0) range = Number(llamalend.formatUnits(_N, 0));
2463
+ const _dDebt = parseUnits(dDebt, this.borrowed_token.decimals);
2464
+
2465
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2466
+ let _health = await contract.health_calculator(user, _totalCollateral, _dDebt, full, range, llamalend.constantOptions) as bigint;
2467
+ _health = _health * BigInt(100);
2468
+
2469
+ return formatUnits(_health);
2470
+ }
2471
+
2472
+ private async leverageCreateLoanHealth(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, full = true): Promise<string> {
2473
+ this._checkLeverageZap();
2474
+ return await this._leverageHealth(userCollateral, userBorrowed, debt, range, full);
2475
+ }
2476
+
2477
+ private async leverageCreateLoanIsApproved(userCollateral: TAmount, userBorrowed: TAmount): Promise<boolean> {
2478
+ this._checkLeverageZap();
2479
+ const collateralAllowance = await hasAllowance(
2480
+ [this.collateral_token.address], [userCollateral], llamalend.signerAddress, this.addresses.controller);
2481
+ const borrowedAllowance = await hasAllowance(
2482
+ [this.borrowed_token.address], [userBorrowed], llamalend.signerAddress, llamalend.constants.ALIASES.leverage_zap);
2483
+
2484
+ return collateralAllowance && borrowedAllowance
2485
+ }
2486
+
2487
+ private async leverageCreateLoanApproveEstimateGas (userCollateral: TAmount, userBorrowed: TAmount): Promise<TGas> {
2488
+ this._checkLeverageZap();
2489
+ const collateralGas = await ensureAllowanceEstimateGas(
2490
+ [this.collateral_token.address], [userCollateral], this.addresses.controller);
2491
+ const borrowedGas = await ensureAllowanceEstimateGas(
2492
+ [this.borrowed_token.address], [userBorrowed], llamalend.constants.ALIASES.leverage_zap);
2493
+
2494
+ if(Array.isArray(collateralGas) && Array.isArray(borrowedGas)) {
2495
+ return [collateralGas[0] + borrowedGas[0], collateralGas[1] + borrowedGas[1]]
2496
+ } else {
2497
+ return (collateralGas as number) + (borrowedGas as number)
2498
+ }
2499
+ }
2500
+
2501
+ private async leverageCreateLoanApprove(userCollateral: TAmount, userBorrowed: TAmount): Promise<string[]> {
2502
+ this._checkLeverageZap();
2503
+ const collateralApproveTx = await ensureAllowance(
2504
+ [this.collateral_token.address], [userCollateral], this.addresses.controller);
2505
+ const borrowedApproveTx = await ensureAllowance(
2506
+ [this.borrowed_token.address], [userBorrowed], llamalend.constants.ALIASES.leverage_zap);
2507
+
2508
+ return [...collateralApproveTx, ...borrowedApproveTx]
2509
+ }
2510
+
2511
+ private async leverageCreateLoanRouteImage(userBorrowed: TAmount, debt: TAmount): Promise<string> {
2512
+ this._checkLeverageZap();
2513
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2514
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2515
+
2516
+ return this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed).pathVizImage;
2517
+ }
2518
+
2519
+ private async _leverageCreateLoan(
2520
+ userCollateral: TAmount,
2521
+ userBorrowed: TAmount,
2522
+ debt: TAmount,
2523
+ range: number,
2524
+ slippage: number,
2525
+ estimateGas: boolean
2526
+ ): Promise<string | TGas> {
2527
+ if (await this.userLoanExists()) throw Error("Loan already created");
2528
+ this._checkRange(range);
2529
+
2530
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2531
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2532
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2533
+ const swapData = this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed);
2534
+ if (slippage !== swapData.slippage) throw Error(`You must call leverage.createLoanExpectedCollateral() with slippage=${slippage} first`);
2535
+ const calldata = await _assembleTxOdos(swapData.pathId as string);
2536
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2537
+ const gas = await contract.create_loan_extended.estimateGas(
2538
+ _userCollateral,
2539
+ _debt,
2540
+ range,
2541
+ llamalend.constants.ALIASES.leverage_zap,
2542
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
2543
+ calldata,
2544
+ { ...llamalend.constantOptions }
2545
+ );
2546
+ if (estimateGas) return smartNumber(gas);
2547
+
2548
+ await llamalend.updateFeeData();
2549
+ const gasLimit = _mulBy1_3(DIGas(gas));
2550
+ return (await contract.create_loan_extended(
2551
+ _userCollateral,
2552
+ _debt,
2553
+ range,
2554
+ llamalend.constants.ALIASES.leverage_zap,
2555
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
2556
+ calldata,
2557
+ { ...llamalend.options, gasLimit }
2558
+ )).hash
2559
+ }
2560
+
2561
+ private async leverageCreateLoanEstimateGas(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage = 0.1): Promise<number> {
2562
+ this._checkLeverageZap();
2563
+ if (!(await this.leverageCreateLoanIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation");
2564
+ return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, slippage, true) as number;
2565
+ }
2566
+
2567
+ private async leverageCreateLoan(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage = 0.1): Promise<string> {
2568
+ this._checkLeverageZap();
2569
+ await this.leverageCreateLoanApprove(userCollateral, userBorrowed);
2570
+ return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, slippage, false) as string;
2571
+ }
2572
+
2573
+ // ---------------- LEVERAGE BORROW MORE ----------------
2574
+
2575
+ private async leverageBorrowMoreMaxRecv(userCollateral: TAmount, userBorrowed: TAmount, address = ""):
2576
+ Promise<{
2577
+ maxDebt: string,
2578
+ maxTotalCollateral: string,
2579
+ userCollateral: string,
2580
+ collateralFromUserBorrowed: string,
2581
+ collateralFromMaxDebt: string,
2582
+ avgPrice: string,
2583
+ }> {
2584
+ // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg)
2585
+ this._checkLeverageZap();
2586
+ address = _getAddress(address);
2587
+ const { _collateral: _stateCollateral, _borrowed: _stateBorrowed, _debt: _stateDebt, _N } = await this._userState(address);
2588
+ if (_stateBorrowed > BigInt(0)) throw Error(`User ${address} is already in liquidation mode`);
2589
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2590
+ const controllerContract = llamalend.contracts[this.addresses.controller].contract;
2591
+ const _borrowedFromStateCollateral = await controllerContract.max_borrowable(_stateCollateral, _N, _stateDebt, llamalend.constantOptions) - _stateDebt;
2592
+ const _userBorrowed = _borrowedFromStateCollateral + parseUnits(userBorrowed, this.borrowed_token.decimals);
2593
+ userBorrowed = formatUnits(_userBorrowed, this.borrowed_token.decimals);
2594
+
2595
+ const oraclePriceBand = await this.oraclePriceBand();
2596
+ let pAvgBN = BN(await this.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band
2597
+ let maxBorrowablePrevBN = BN(0);
2598
+ let maxBorrowableBN = BN(0);
2599
+ let _userEffectiveCollateral = BigInt(0);
2600
+ let _maxLeverageCollateral = BigInt(0);
2601
+
2602
+ const contract = llamalend.contracts[llamalend.constants.ALIASES.leverage_zap].contract;
2603
+ for (let i = 0; i < 5; i++) {
2604
+ maxBorrowablePrevBN = maxBorrowableBN;
2605
+ _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals);
2606
+ let _maxBorrowable = await contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, _N, fromBN(pAvgBN));
2607
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000);
2608
+ if (_maxBorrowable === BigInt(0)) break;
2609
+ maxBorrowableBN = toBN(_maxBorrowable, this.borrowed_token.decimals);
2610
+
2611
+ if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) {
2612
+ maxBorrowableBN = maxBorrowablePrevBN;
2613
+ break;
2614
+ }
2615
+
2616
+ // additionalCollateral = (userBorrowed / p) + leverageCollateral
2617
+ const _maxAdditionalCollateral = BigInt(await _getExpectedOdos(
2618
+ this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable + _userBorrowed, this.addresses.amm));
2619
+ pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.collateral_token.decimals));
2620
+ _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals);
2621
+ }
2622
+
2623
+ if (maxBorrowableBN.eq(0)) _userEffectiveCollateral = BigInt(0);
2624
+ const _maxTotalCollateral = _userEffectiveCollateral + _maxLeverageCollateral
2625
+ let _maxBorrowable = await controllerContract.max_borrowable(_stateCollateral + _maxTotalCollateral, _N, _stateDebt, llamalend.constantOptions) - _stateDebt;
2626
+ _maxBorrowable = _maxBorrowable * BigInt(998) / BigInt(1000);
2627
+
2628
+ return {
2629
+ maxDebt: formatUnits(_maxBorrowable, this.borrowed_token.decimals),
2630
+ maxTotalCollateral: formatUnits(_maxTotalCollateral, this.collateral_token.decimals),
2631
+ userCollateral: formatNumber(userCollateral, this.collateral_token.decimals),
2632
+ collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.collateral_token.decimals),
2633
+ collateralFromMaxDebt: formatUnits(_maxLeverageCollateral, this.collateral_token.decimals),
2634
+ avgPrice: pAvgBN.toString(),
2635
+ };
2636
+ }
2637
+
2638
+ private async leverageBorrowMoreExpectedCollateral(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, slippage = 0.1, address = ""):
2639
+ Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, avgPrice: string }> {
2640
+ this._checkLeverageZap();
2641
+ address = _getAddress(address);
2642
+ const _dDebt = parseUnits(dDebt, this.borrowed_token.decimals);
2643
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2644
+ await this._setSwapDataToCache(this.addresses.borrowed_token, this.addresses.collateral_token, _dDebt + _userBorrowed, slippage);
2645
+ const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } =
2646
+ await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, address);
2647
+ return {
2648
+ totalCollateral: formatUnits(_totalCollateral, this.collateral_token.decimals),
2649
+ userCollateral: formatUnits(_userCollateral, this.collateral_token.decimals),
2650
+ collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.collateral_token.decimals),
2651
+ collateralFromDebt: formatUnits(_collateralFromDebt, this.collateral_token.decimals),
2652
+ avgPrice,
2653
+ }
2654
+ }
2655
+
2656
+ private async leverageBorrowMorePriceImpact(userBorrowed: TAmount, dDebt: TAmount): Promise<string> {
2657
+ this._checkLeverageZap();
2658
+ const _dDebt = parseUnits(dDebt, this.borrowed_token.decimals);
2659
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2660
+ return this._getSwapDataFromCache(this.addresses.borrowed_token, _dDebt + _userBorrowed).priceImpact.toString();
2661
+ }
2662
+
2663
+ private async leverageBorrowMoreBands(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address = ""): Promise<[number, number]> {
2664
+ address = _getAddress(address);
2665
+ this._checkLeverageZap();
2666
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, dDebt, -1, address);
2667
+
2668
+ return [Number(_n2), Number(_n1)];
2669
+ }
2670
+
2671
+ private async leverageBorrowMorePrices(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address = ""): Promise<string[]> {
2672
+ address = _getAddress(address);
2673
+ this._checkLeverageZap();
2674
+ const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, dDebt, -1, address);
2675
+
2676
+ return await this._getPrices(_n2, _n1);
2677
+ }
2678
+
2679
+ private async leverageBorrowMoreHealth(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, full = true, address = ""): Promise<string> {
2680
+ this._checkLeverageZap();
2681
+ address = _getAddress(address);
2682
+ return await this._leverageHealth(userCollateral, userBorrowed, dDebt, -1, full, address);
2683
+ }
2684
+
2685
+ private async leverageBorrowMoreRouteImage(userBorrowed: TAmount, debt: TAmount): Promise<string> {
2686
+ this._checkLeverageZap();
2687
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2688
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2689
+
2690
+ return this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed).pathVizImage;
2691
+ }
2692
+
2693
+ private async _leverageBorrowMore(
2694
+ userCollateral: TAmount,
2695
+ userBorrowed: TAmount,
2696
+ debt: TAmount,
2697
+ slippage: number,
2698
+ estimateGas: boolean
2699
+ ): Promise<string | TGas> {
2700
+ if (!(await this.userLoanExists())) throw Error("Loan does not exist");
2701
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2702
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2703
+ const _debt = parseUnits(debt, this.borrowed_token.decimals);
2704
+ const swapData = this._getSwapDataFromCache(this.addresses.borrowed_token, _debt + _userBorrowed);
2705
+ if (slippage !== swapData.slippage) throw Error(`You must call leverage.borrowMoreExpectedCollateral() with slippage=${slippage} first`)
2706
+ const calldata = await _assembleTxOdos(swapData.pathId as string);
2707
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2708
+ const gas = await contract.borrow_more_extended.estimateGas(
2709
+ _userCollateral,
2710
+ _debt,
2711
+ llamalend.constants.ALIASES.leverage_zap,
2712
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
2713
+ calldata,
2714
+ { ...llamalend.constantOptions }
2715
+ );
2716
+ if (estimateGas) return smartNumber(gas);
2717
+
2718
+ await llamalend.updateFeeData();
2719
+ const gasLimit = _mulBy1_3(DIGas(gas));
2720
+
2721
+ return (await contract.borrow_more_extended(
2722
+ _userCollateral,
2723
+ _debt,
2724
+ llamalend.constants.ALIASES.leverage_zap,
2725
+ [0, parseUnits(this._getMarketId(), 0), _userBorrowed],
2726
+ calldata,
2727
+ { ...llamalend.options, gasLimit }
2728
+ )).hash
2729
+ }
2730
+
2731
+ private async leverageBorrowMoreEstimateGas(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise<number> {
2732
+ this._checkLeverageZap();
2733
+ if (!(await this.leverageCreateLoanIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation");
2734
+ return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, slippage, true) as number;
2735
+ }
2736
+
2737
+ private async leverageBorrowMore(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise<string> {
2738
+ this._checkLeverageZap();
2739
+ await this.leverageCreateLoanApprove(userCollateral, userBorrowed);
2740
+ return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, slippage, false) as string;
2741
+ }
2742
+
2743
+ // ---------------- LEVERAGE REPAY ----------------
2744
+
2745
+ private _leverageRepayExpectedBorrowed = (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount):
2746
+ { _totalBorrowed: bigint, _borrowedFromStateCollateral: bigint, _borrowedFromUserCollateral: bigint, avgPrice: string } => {
2747
+ this._checkLeverageZap();
2748
+ const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2749
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2750
+ let _borrowedExpected = BigInt(0);
2751
+ let _borrowedFromStateCollateral = BigInt(0);
2752
+ let _borrowedFromUserCollateral = BigInt(0);
2753
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
2754
+ _borrowedExpected = BigInt(this._getSwapDataFromCache(this.addresses.collateral_token, _stateCollateral + _userCollateral).outAmounts[0]);
2755
+ _borrowedFromStateCollateral = _stateCollateral * BigInt(10 ** 18) / (_stateCollateral + _userCollateral) * _borrowedExpected / BigInt(10 ** 18);
2756
+ _borrowedFromUserCollateral = _borrowedExpected - _borrowedFromStateCollateral;
2757
+ }
2758
+ const _totalBorrowed = _borrowedExpected + parseUnits(userBorrowed, this.borrowed_token.decimals);
2759
+ const avgPrice = toBN(_borrowedExpected, this.borrowed_token.decimals).div(toBN(_stateCollateral + _userCollateral, this.collateral_token.decimals)).toString();
2760
+
2761
+ return { _totalBorrowed, _borrowedFromStateCollateral, _borrowedFromUserCollateral, avgPrice }
2762
+ };
2763
+
2764
+ private leverageRepayExpectedBorrowed = async (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage = 0.1):
2765
+ Promise<{ totalBorrowed: string, borrowedFromStateCollateral: string, borrowedFromUserCollateral: string, userBorrowed: string, avgPrice: string }> => {
2766
+ this._checkLeverageZap();
2767
+ const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2768
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2769
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
2770
+ await this._setSwapDataToCache(this.addresses.collateral_token, this.addresses.borrowed_token, _stateCollateral + _userCollateral, slippage);
2771
+ }
2772
+ const { _totalBorrowed, _borrowedFromStateCollateral, _borrowedFromUserCollateral, avgPrice } =
2773
+ this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed);
2774
+
2775
+ return {
2776
+ totalBorrowed: formatUnits(_totalBorrowed, this.borrowed_token.decimals),
2777
+ borrowedFromStateCollateral: formatUnits(_borrowedFromStateCollateral, this.borrowed_token.decimals),
2778
+ borrowedFromUserCollateral: formatUnits(_borrowedFromUserCollateral, this.borrowed_token.decimals),
2779
+ userBorrowed: formatNumber(userBorrowed, this.borrowed_token.decimals),
2780
+ avgPrice,
2781
+ }
2782
+ };
2783
+
2784
+ private async leverageRepayPriceImpact(stateCollateral: TAmount, userCollateral: TAmount): Promise<string> {
2785
+ this._checkLeverageZap();
2786
+ const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2787
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2788
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
2789
+ return this._getSwapDataFromCache(this.addresses.collateral_token, _stateCollateral + _userCollateral).priceImpact.toString();
2790
+ } else {
2791
+ return "0.0"
2792
+ }
2793
+ }
2794
+
2795
+ private async leverageRepayIsFull(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise<boolean> {
2796
+ this._checkLeverageZap();
2797
+ address = _getAddress(address);
2798
+ const { _borrowed: _stateBorrowed, _debt } = await this._userState(address);
2799
+ const { _totalBorrowed } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed);
2800
+
2801
+ return _stateBorrowed + _totalBorrowed > _debt;
2802
+ }
2803
+
2804
+ private async leverageRepayIsAvailable(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise<boolean> {
2805
+ // 0. const { collateral, stablecoin, debt } = await this.userState(address);
2806
+ // 1. maxCollateral for deleverage is collateral from line above.
2807
+ // 2. If user is underwater (stablecoin > 0), only full repayment is available:
2808
+ // await this.deleverageRepayStablecoins(deleverageCollateral) + stablecoin > debt
2809
+ this._checkLeverageZap();
2810
+ address = _getAddress(address);
2811
+ const { collateral, borrowed, debt } = await this.userState(address);
2812
+ // Loan does not exist
2813
+ if (BN(debt).eq(0)) return false;
2814
+ // Can't spend more than user has
2815
+ if (BN(stateCollateral).gt(collateral)) return false;
2816
+ // Only full repayment and closing the position is available if user is underwater+
2817
+ if (BN(borrowed).gt(0)) return await this.leverageRepayIsFull(stateCollateral, userCollateral, userBorrowed, address);
2818
+
2819
+ return true;
2820
+ }
2821
+
2822
+ private _leverageRepayBands = memoize( async (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address: string): Promise<[bigint, bigint]> => {
2823
+ address = _getAddress(address);
2824
+ if (!(await this.leverageRepayIsAvailable(stateCollateral, userCollateral, userBorrowed, address))) return [parseUnits(0, 0), parseUnits(0, 0)];
2825
+
2826
+ const _stateRepayCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2827
+ const { _collateral: _stateCollateral, _debt: _stateDebt, _N } = await this._userState(address);
2828
+ if (_stateDebt == BigInt(0)) throw Error(`Loan for ${address} does not exist`);
2829
+ if (_stateCollateral < _stateRepayCollateral) throw Error(`Can't use more collateral than user's position has (${_stateRepayCollateral}) > ${_stateCollateral})`);
2830
+
2831
+ let _n1 = parseUnits(0, 0);
2832
+ let _n2 = parseUnits(0, 0);
2833
+ const { _totalBorrowed: _repayExpected } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed);
2834
+ try {
2835
+ _n1 = await llamalend.contracts[this.addresses.controller].contract.calculate_debt_n1(_stateCollateral - _stateRepayCollateral, _stateDebt - _repayExpected, _N);
2836
+ _n2 = _n1 + (_N - BigInt(1));
2837
+ } catch (e) {
2838
+ console.log("Full repayment");
2839
+ }
2840
+
2841
+ return [_n2, _n1];
2842
+ },
2843
+ {
2844
+ promise: true,
2845
+ maxAge: 5 * 60 * 1000, // 5m
2846
+ });
2847
+
2848
+ private async leverageRepayBands(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise<[number, number]> {
2849
+ this._checkLeverageZap();
2850
+ const [_n2, _n1] = await this._leverageRepayBands(stateCollateral, userCollateral, userBorrowed, address);
2851
+
2852
+ return [Number(_n2), Number(_n1)];
2853
+ }
2854
+
2855
+ private async leverageRepayPrices(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise<string[]> {
2856
+ this._checkLeverageZap();
2857
+ const [_n2, _n1] = await this._leverageRepayBands(stateCollateral, userCollateral, userBorrowed, address);
2858
+
2859
+ return await this._getPrices(_n2, _n1);
2860
+ }
2861
+
2862
+ private async leverageRepayHealth(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, full = true, address = ""): Promise<string> {
2863
+ this._checkLeverageZap();
2864
+ address = _getAddress(address);
2865
+ const { _borrowed: _stateBorrowed, _debt, _N } = await this._userState(address);
2866
+ if (_stateBorrowed > BigInt(0)) return "0.0";
2867
+ if (!(await this.leverageRepayIsAvailable(stateCollateral, userCollateral, userBorrowed, address))) return "0.0";
2868
+
2869
+ const { _totalBorrowed } = this._leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed);
2870
+ const _dCollateral = parseUnits(stateCollateral, this.collateral_token.decimals) * BigInt(-1);
2871
+ const _dDebt = _totalBorrowed * BigInt(-1);
2872
+
2873
+ if (_debt + _dDebt <= BigInt(0)) return "0.0";
2874
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2875
+ let _health = await contract.health_calculator(address, _dCollateral, _dDebt, full, _N, llamalend.constantOptions) as bigint;
2876
+ _health = _health * BigInt(100);
2877
+
2878
+ return llamalend.formatUnits(_health);
2879
+ }
2880
+
2881
+ private async leverageRepayIsApproved(userCollateral: TAmount, userBorrowed: TAmount): Promise<boolean> {
2882
+ this._checkLeverageZap();
2883
+ return await hasAllowance(
2884
+ [this.collateral_token.address, this.borrowed_token.address],
2885
+ [userCollateral, userBorrowed],
2886
+ llamalend.signerAddress,
2887
+ llamalend.constants.ALIASES.leverage_zap
2888
+ );
2889
+ }
2890
+
2891
+ private async leverageRepayApproveEstimateGas (userCollateral: TAmount, userBorrowed: TAmount): Promise<TGas> {
2892
+ this._checkLeverageZap();
2893
+ return await ensureAllowanceEstimateGas(
2894
+ [this.collateral_token.address, this.borrowed_token.address],
2895
+ [userCollateral, userBorrowed],
2896
+ llamalend.constants.ALIASES.leverage_zap
2897
+ );
2898
+ }
2899
+
2900
+ private async leverageRepayApprove(userCollateral: TAmount, userBorrowed: TAmount): Promise<string[]> {
2901
+ this._checkLeverageZap();
2902
+ return await ensureAllowance(
2903
+ [this.collateral_token.address, this.borrowed_token.address],
2904
+ [userCollateral, userBorrowed],
2905
+ llamalend.constants.ALIASES.leverage_zap
2906
+ );
2907
+ }
2908
+
2909
+ private async leverageRepayRouteImage(stateCollateral: TAmount, userCollateral: TAmount): Promise<string> {
2910
+ this._checkLeverageZap();
2911
+ const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2912
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2913
+
2914
+ return this._getSwapDataFromCache(this.addresses.collateral_token, _stateCollateral + _userCollateral).pathVizImage;
2915
+ }
2916
+
2917
+ private async _leverageRepay(
2918
+ stateCollateral: TAmount,
2919
+ userCollateral: TAmount,
2920
+ userBorrowed: TAmount,
2921
+ slippage: number,
2922
+ estimateGas: boolean
2923
+ ): Promise<string | TGas> {
2924
+ if (!(await this.userLoanExists())) throw Error("Loan does not exist");
2925
+ const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals);
2926
+ const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals);
2927
+ const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals);
2928
+ let calldata = "0x";
2929
+ if (_stateCollateral + _userCollateral > BigInt(0)) {
2930
+ const swapData = this._getSwapDataFromCache(this.addresses.collateral_token, _stateCollateral + _userCollateral);
2931
+ if (slippage !== swapData.slippage) throw Error(`You must call leverage.repayExpectedBorrowed() with slippage=${slippage} first`)
2932
+ calldata = await _assembleTxOdos(swapData.pathId as string);
2933
+ }
2934
+
2935
+ console.log('params', [0, parseUnits(this._getMarketId(), 0), _userCollateral, _userBorrowed], calldata)
2936
+ const contract = llamalend.contracts[this.addresses.controller].contract;
2937
+ const gas = await contract.repay_extended.estimateGas(
2938
+ llamalend.constants.ALIASES.leverage_zap,
2939
+ [0, parseUnits(this._getMarketId(), 0), _userCollateral, _userBorrowed],
2940
+ calldata
2941
+ );
2942
+ if (estimateGas) return smartNumber(gas);
2943
+
2944
+ await llamalend.updateFeeData();
2945
+ const gasLimit = _mulBy1_3(DIGas(gas));
2946
+
2947
+ return (await contract.repay_extended(
2948
+ llamalend.constants.ALIASES.leverage_zap,
2949
+ [0, parseUnits(this._getMarketId(), 0), _userCollateral, _userBorrowed],
2950
+ calldata,
2951
+ { ...llamalend.options, gasLimit }
2952
+ )).hash
2953
+ }
2954
+
2955
+ private async leverageRepayEstimateGas(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage = 0.1): Promise<number> {
2956
+ this._checkLeverageZap();
2957
+ if (!(await this.leverageRepayIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation");
2958
+ return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, slippage, true) as number;
2959
+ }
2960
+
2961
+ private async leverageRepay(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage = 0.1): Promise<string> {
2962
+ this._checkLeverageZap();
2963
+ await this.leverageRepayApprove(userCollateral, userBorrowed);
2964
+ return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, slippage, false) as string;
2965
+ }
2966
+
2967
+ public async currentLeverage(userAddress = ''): Promise<string> {
2968
+ userAddress = _getAddress(userAddress);
2969
+ const [userCollateral, _current_collateral_estimation] = await Promise.all([
2970
+ _getUserCollateral(llamalend.constants.NETWORK_NAME, this.addresses.controller, userAddress),
2971
+ llamalend.contracts[this.addresses.amm].contract.get_y_up(userAddress),
2972
+ ]);
2973
+
2974
+ const total_deposit_from_user = userCollateral.total_deposit_from_user;
2975
+ const current_collateral_estimation = llamalend.formatUnits(_current_collateral_estimation, this.collateral_token.decimals);
2976
+
2977
+
2978
+ return BN(current_collateral_estimation).div(total_deposit_from_user).toString();
2979
+ }
2980
+
2981
+ public async currentPnL(userAddress = ''): Promise<Record<string, string>> {
2982
+ userAddress = _getAddress(userAddress);
2983
+
2984
+ const calls = [
2985
+ llamalend.contracts[this.addresses.amm].multicallContract.get_y_up(userAddress),
2986
+ llamalend.contracts[this.addresses.controller].multicallContract.user_state(userAddress, llamalend.constantOptions),
2987
+ llamalend.contracts[this.addresses.amm].multicallContract.price_oracle(userAddress),
2988
+ ];
2989
+
2990
+ const [currentCollateralEstimation, userState, oraclePrice] = await llamalend.multicallProvider.all(calls) as [bigint, bigint[],bigint];
2991
+
2992
+ if(!(currentCollateralEstimation || userState || oraclePrice)) {
2993
+ throw new Error('Multicall error')
2994
+ }
2995
+
2996
+ const debt = userState[2];
2997
+
2998
+ const userCollateral = await _getUserCollateral(llamalend.constants.NETWORK_NAME, this.addresses.controller, userAddress);
2999
+ const totalDepositUsdValue = userCollateral.total_deposit_usd_value;
3000
+
3001
+ const currentCollateralEstimationFormatted = llamalend.formatUnits(currentCollateralEstimation, this.collateral_token.decimals);
3002
+ const oraclePriceFormatted = llamalend.formatUnits(oraclePrice, 18);
3003
+ const debtFormatted = llamalend.formatUnits(debt, 18);
3004
+
3005
+ const currentPosition = BN(currentCollateralEstimationFormatted)
3006
+ .times(oraclePriceFormatted)
3007
+ .minus(debtFormatted)
3008
+ .toFixed(this.collateral_token.decimals)
3009
+
3010
+ const percentage = BN(currentPosition)
3011
+ .div(totalDepositUsdValue)
3012
+ .minus(1)
3013
+ .times(100)
3014
+ .toString();
3015
+
3016
+ return {
3017
+ currentPosition: currentPosition,
3018
+ deposited: totalDepositUsdValue.toString(),
3019
+ percentage: percentage,
3020
+ };
3021
+ }
3022
+ }