@curvefi/llamalend-api 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ import { INetworkName, IMarketData, IMarketDataAPI } from "../interfaces";
2
+ export type RatesResult = {
3
+ borrowApr: string;
4
+ lendApr: string;
5
+ borrowApy: string;
6
+ lendApy: string;
7
+ };
8
+ /**
9
+ * Computes borrow/lend APR and APY from a raw per-second rate and current debt/cap.
10
+ * borrowApy = e^(rate * 365 * 86400) - 1
11
+ * lendApy = (debt * e^(rate * 365 * 86400) - debt) / cap
12
+ */
13
+ export declare const computeRatesFromRate: (_rate: bigint, debt: string | number, cap: string | number) => RatesResult;
14
+ export declare const fetchMarketDataByVault: (networkName: INetworkName, vaultAddress: string, getData: (network: INetworkName) => Promise<IMarketData>) => Promise<IMarketDataAPI>;
@@ -0,0 +1,33 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { BN, toBN } from "../utils";
11
+ /**
12
+ * Computes borrow/lend APR and APY from a raw per-second rate and current debt/cap.
13
+ * borrowApy = e^(rate * 365 * 86400) - 1
14
+ * lendApy = (debt * e^(rate * 365 * 86400) - debt) / cap
15
+ */
16
+ export const computeRatesFromRate = (_rate, debt, cap) => {
17
+ const annualFactor = toBN(_rate).times(365).times(86400);
18
+ const expFactor = Math.pow(Math.E, annualFactor.toNumber());
19
+ const borrowApr = annualFactor.times(100).toString();
20
+ const borrowApy = String((expFactor - 1) * 100);
21
+ const lendAprRaw = annualFactor.times(debt).div(cap).times(100);
22
+ const lendApr = lendAprRaw.isNaN() ? "0" : lendAprRaw.toString();
23
+ const lendApyRaw = BN(debt).times(expFactor).minus(debt).div(cap).times(100);
24
+ const lendApy = lendApyRaw.isNaN() ? "0" : lendApyRaw.toString();
25
+ return { borrowApr, lendApr, borrowApy, lendApy };
26
+ };
27
+ export const fetchMarketDataByVault = (networkName, vaultAddress, getData) => __awaiter(void 0, void 0, void 0, function* () {
28
+ const response = yield getData(networkName);
29
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === vaultAddress.toLowerCase());
30
+ if (!market)
31
+ throw new Error("Market not found in API");
32
+ return market;
33
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curvefi/llamalend-api",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "JavaScript library for Curve Lending",
5
5
  "main": "lib/index.js",
6
6
  "author": "Macket",
package/src/interfaces.ts CHANGED
@@ -193,6 +193,7 @@ export interface IMarketDataAPI {
193
193
  totalSupplied: Total;
194
194
  borrowed: Total;
195
195
  availableToBorrow: Total;
196
+ borrowCap: Total;
196
197
  lendingVaultUrls: LendingVaultUrls;
197
198
  usdTotal: number;
198
199
  ammBalances: AmmBalances;
@@ -166,20 +166,21 @@ export class LendMarketTemplate<V extends 'v1' | 'v2' = 'v1' | 'v2'> {
166
166
  }
167
167
 
168
168
  this.stats = {
169
- parameters: stats.statsParameters.bind(this),
170
- rates: stats.statsRates.bind(this),
171
- futureRates: stats.statsFutureRates.bind(this),
172
- balances: stats.statsBalances.bind(this),
173
- bandsInfo: stats.statsBandsInfo.bind(this),
174
- bandBalances: stats.statsBandBalances.bind(this),
175
- bandsBalances: stats.statsBandsBalances.bind(this),
176
- totalDebt: stats.statsTotalDebt.bind(this),
177
- ammBalances: stats.statsAmmBalances.bind(this),
178
- capAndAvailable: stats.statsCapAndAvailable.bind(this),
169
+ parameters: stats.statsParameters.bind(stats),
170
+ rates: stats.statsRates.bind(stats),
171
+ futureRates: stats.statsFutureRates.bind(stats),
172
+ balances: stats.statsBalances.bind(stats),
173
+ bandsInfo: stats.statsBandsInfo.bind(stats),
174
+ bandBalances: stats.statsBandBalances.bind(stats),
175
+ bandsBalances: stats.statsBandsBalances.bind(stats),
176
+ totalDebt: stats.statsTotalDebt.bind(stats),
177
+ ammBalances: stats.statsAmmBalances.bind(stats),
178
+ capAndAvailable: stats.statsCapAndAvailable.bind(stats),
179
+ adminPercentage: stats.statsAdminPercentage.bind(stats),
179
180
  } as StatsForVersion<V>
180
181
 
181
182
  this.wallet = {
182
- balances: wallet.balances.bind(this),
183
+ balances: wallet.balances.bind(wallet),
183
184
  }
184
185
 
185
186
  this.prices = {
@@ -17,5 +17,6 @@ export interface IStatsV1 {
17
17
  bandsBalances: () => Promise<{ [index: number]: { borrowed: string, collateral: string } }>,
18
18
  totalDebt: (isGetter?: boolean, useAPI?: boolean) => Promise<string>,
19
19
  ammBalances: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowed: string, collateral: string }>,
20
- capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ cap: string, available: string }>,
20
+ capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }>,
21
+ adminPercentage: () => Promise<string>,
21
22
  }
@@ -17,5 +17,6 @@ export interface IStatsV2 {
17
17
  bandsBalances: () => Promise<{ [index: number]: { borrowed: string, collateral: string } }>,
18
18
  totalDebt: (isGetter?: boolean, useAPI?: boolean) => Promise<string>,
19
19
  ammBalances: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowed: string, collateral: string }>,
20
- capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ cap: string, available: string }>,
20
+ capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }>,
21
+ adminPercentage: () => Promise<string>,
21
22
  }
@@ -1,9 +1,8 @@
1
1
  import memoize from "memoizee";
2
- import {TAmount} from "../../../interfaces";
2
+ import {TAmount, IMarketDataAPI} from "../../../interfaces";
3
3
  import type { LendMarketTemplate } from "../../LendMarketTemplate";
4
4
  import {
5
5
  parseUnits,
6
- BN,
7
6
  toBN,
8
7
  formatUnits,
9
8
  formatNumber,
@@ -11,6 +10,8 @@ import {
11
10
  import {Llamalend} from "../../../llamalend";
12
11
  import {_getMarketsData} from "../../../external-api";
13
12
  import {cacheKey, cacheStats} from "../../../cache";
13
+ import { computeRatesFromRate, fetchMarketDataByVault } from "../../utils";
14
+ const PRECISION = BigInt("1000000000000000000"); // 1e18
14
15
 
15
16
  export class StatsBaseModule {
16
17
  protected market: LendMarketTemplate;
@@ -21,6 +22,63 @@ export class StatsBaseModule {
21
22
  this.llamalend = market.getLlamalend();
22
23
  }
23
24
 
25
+ protected async _fetchMarketDataFromAPI(): Promise<IMarketDataAPI> {
26
+ return fetchMarketDataByVault(
27
+ this.llamalend.constants.NETWORK_NAME,
28
+ this.market.addresses.vault,
29
+ _getMarketsData
30
+ );
31
+ }
32
+
33
+ protected _fetchAdminPercentage = async (): Promise<bigint> => {
34
+ return BigInt(0)
35
+ }
36
+
37
+ protected _fetchAdminFee = async (): Promise<bigint> => {
38
+ return this.llamalend.contracts[this.market.addresses.amm].contract.admin_fee(this.llamalend.constantOptions);
39
+ }
40
+
41
+ protected async _getAdminFeesXY(isGetter: boolean): Promise<[bigint, bigint]> {
42
+ if(isGetter) {
43
+ return [
44
+ cacheStats.get(cacheKey(this.market.addresses.amm, 'admin_fees_x')),
45
+ cacheStats.get(cacheKey(this.market.addresses.amm, 'admin_fees_y')),
46
+ ];
47
+ }
48
+ const ammContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
49
+ const [_fee_x, _fee_y] = await this.llamalend.multicallProvider.all([
50
+ ammContract.admin_fees_x(),
51
+ ammContract.admin_fees_y(),
52
+ ]) as [bigint, bigint];
53
+ cacheStats.set(cacheKey(this.market.addresses.amm, 'admin_fees_x'), _fee_x);
54
+ cacheStats.set(cacheKey(this.market.addresses.amm, 'admin_fees_y'), _fee_y);
55
+ return [_fee_x, _fee_y];
56
+ }
57
+
58
+ private _getRate = async (isGetter = true): Promise<bigint> => {
59
+ if (isGetter) {
60
+ const _rate: bigint = cacheStats.get(cacheKey(this.market.addresses.amm, 'rate'));
61
+ const _adminPercentage = await this._fetchAdminPercentage();
62
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
63
+ } else {
64
+ const [_rate, _adminPercentage] = await Promise.all([
65
+ this.llamalend.contracts[this.market.addresses.amm].contract.rate(this.llamalend.constantOptions),
66
+ this._fetchAdminPercentage(),
67
+ ]);
68
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'rate'), _rate);
69
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
70
+ }
71
+ }
72
+
73
+ private _getFutureRate = async (_dReserves: bigint, _dDebt: bigint): Promise<bigint> => {
74
+ const mpContract = this.llamalend.contracts[this.market.addresses.monetary_policy].contract;
75
+ const [_rate, _adminPercentage] = await Promise.all([
76
+ mpContract.future_rate(this.market.addresses.controller, _dReserves, _dDebt),
77
+ this._fetchAdminPercentage(),
78
+ ]);
79
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
80
+ }
81
+
24
82
  public statsParameters = memoize(async (): Promise<{
25
83
  fee: string, // %
26
84
  admin_fee: string, // %
@@ -32,16 +90,17 @@ export class StatsBaseModule {
32
90
  const llammaContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
33
91
  const controllerContract = this.llamalend.contracts[this.market.addresses.controller].multicallContract;
34
92
 
35
- const calls = [
36
- llammaContract.fee(),
37
- llammaContract.admin_fee(), // TODO: removed
38
- controllerContract.liquidation_discount(),
39
- controllerContract.loan_discount(),
40
- llammaContract.get_base_price(),
41
- llammaContract.A(),
42
- ]
93
+ const [[_fee, _liquidation_discount, _loan_discount, _base_price, _A], _admin_fee] = await Promise.all([
94
+ this.llamalend.multicallProvider.all([
95
+ llammaContract.fee(),
96
+ controllerContract.liquidation_discount(),
97
+ controllerContract.loan_discount(),
98
+ llammaContract.get_base_price(),
99
+ llammaContract.A(),
100
+ ]),
101
+ this._fetchAdminFee(),
102
+ ]) as [bigint[], bigint];
43
103
 
44
- const [_fee, _admin_fee, _liquidation_discount, _loan_discount, _base_price, _A]: bigint[] = await this.llamalend.multicallProvider.all(calls) as bigint[];
45
104
  const A = formatUnits(_A, 0)
46
105
  const base_price = formatUnits(_base_price)
47
106
  const [fee, admin_fee, liquidation_discount, loan_discount] = [_fee, _admin_fee, _liquidation_discount, _loan_discount]
@@ -53,55 +112,20 @@ export class StatsBaseModule {
53
112
  maxAge: 5 * 60 * 1000, // 5m
54
113
  });
55
114
 
56
- private _getRate = async (isGetter = true): Promise<bigint> => {
57
- let _rate;
58
- if(isGetter) {
59
- _rate = cacheStats.get(cacheKey(this.market.addresses.amm, 'rate'));
60
- } else {
61
- _rate = await this.llamalend.contracts[this.market.addresses.amm].contract.rate(this.llamalend.constantOptions);
62
- cacheStats.set(cacheKey(this.market.addresses.controller, 'rate'), _rate);
63
- }
64
- return _rate;
65
- }
66
-
67
- private _getFutureRate = async (_dReserves: bigint, _dDebt: bigint): Promise<bigint> => {
68
- const mpContract = this.llamalend.contracts[this.market.addresses.monetary_policy].contract;
69
- return await mpContract.future_rate(this.market.addresses.controller, _dReserves, _dDebt);
70
- }
71
-
72
115
  public async statsRates(isGetter = true, useAPI = false): Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}> {
73
116
  if(useAPI) {
74
- const response = await _getMarketsData(this.llamalend.constants.NETWORK_NAME);
75
-
76
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase())
77
-
78
- if(market) {
79
- return {
80
- borrowApr: (market.rates.borrowApr * 100).toString(),
81
- lendApr: (market.rates.lendApr * 100).toString(),
82
- borrowApy: (market.rates.borrowApy * 100).toString(),
83
- lendApy: (market.rates.lendApy * 100).toString(),
84
- }
85
- } else {
86
- throw new Error('Market not found in API')
87
- }
117
+ const market = await this._fetchMarketDataFromAPI();
118
+ return {
119
+ borrowApr: (market.rates.borrowApr * 100).toString(),
120
+ lendApr: (market.rates.lendApr * 100).toString(),
121
+ borrowApy: (market.rates.borrowApy * 100).toString(),
122
+ lendApy: (market.rates.lendApy * 100).toString(),
123
+ };
88
124
  } else {
89
125
  const _rate = await this._getRate(isGetter);
90
- const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
91
- // borrowApy = e**(rate*365*86400) - 1
92
- const borrowApy = String(((2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber()) - 1) * 100);
93
- let lendApr = "0";
94
- let lendApy = "0";
95
- const debt = await this.statsTotalDebt(isGetter);
96
- if (Number(debt) > 0) {
97
- const { cap } = await this.statsCapAndAvailable(isGetter);
98
- lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
99
- // lendApy = (debt * e**(rate*365*86400) - debt) / cap
100
- const debtInAYearBN = BN(debt).times(2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber());
101
- lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
102
- }
103
-
104
- return { borrowApr, lendApr, borrowApy, lendApy }
126
+ const debt = await this.statsTotalDebt(isGetter, false);
127
+ const { totalAssets } = Number(debt) > 0 ? await this.statsCapAndAvailable(isGetter, false) : { totalAssets: "0" };
128
+ return computeRatesFromRate(_rate, debt, totalAssets);
105
129
  }
106
130
  }
107
131
 
@@ -109,34 +133,22 @@ export class StatsBaseModule {
109
133
  const _dReserves = parseUnits(dReserves, this.market.borrowed_token.decimals);
110
134
  const _dDebt = parseUnits(dDebt, this.market.borrowed_token.decimals);
111
135
  const _rate = await this._getFutureRate(_dReserves, _dDebt);
112
- const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
113
- // borrowApy = e**(rate*365*86400) - 1
114
- const borrowApy = String(((2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber()) - 1) * 100);
115
- let lendApr = "0";
116
- let lendApy = "0";
117
136
  const debt = Number(await this.statsTotalDebt()) + Number(dDebt);
118
- if (Number(debt) > 0) {
119
- const cap = Number((await this.statsCapAndAvailable(true, useAPI)).cap) + Number(dReserves);
120
- lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
121
- // lendApy = (debt * e**(rate*365*86400) - debt) / cap
122
- const debtInAYearBN = BN(debt).times(2.718281828459 ** (toBN(_rate).times(365).times(86400)).toNumber());
123
- lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
124
- }
125
-
126
- return { borrowApr, lendApr, borrowApy, lendApy }
137
+ const cap = Number((await this.statsCapAndAvailable(true, useAPI)).totalAssets) + Number(dReserves);
138
+ return computeRatesFromRate(_rate, debt, cap);
127
139
  }
128
140
 
129
141
  public async statsBalances(): Promise<[string, string]> {
130
142
  const borrowedContract = this.llamalend.contracts[this.market.borrowed_token.address].multicallContract;
131
143
  const collateralContract = this.llamalend.contracts[this.market.collateral_token.address].multicallContract;
132
- const ammContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
133
- const calls = [
134
- borrowedContract.balanceOf(this.market.addresses.amm),
135
- collateralContract.balanceOf(this.market.addresses.amm),
136
- ammContract.admin_fees_x(), // TODO: always 0
137
- ammContract.admin_fees_y(), // TODO: always 0
138
- ]
139
- const [_borrowedBalance, _collateralBalance, _borrowedAdminFees, _collateralAdminFees]: bigint[] = await this.llamalend.multicallProvider.all(calls);
144
+
145
+ const [[_borrowedBalance, _collateralBalance], [_borrowedAdminFees, _collateralAdminFees]] = await Promise.all([
146
+ this.llamalend.multicallProvider.all([
147
+ borrowedContract.balanceOf(this.market.addresses.amm),
148
+ collateralContract.balanceOf(this.market.addresses.amm),
149
+ ]),
150
+ this._getAdminFeesXY(false),
151
+ ]) as [bigint[], [bigint, bigint]];
140
152
 
141
153
  return [
142
154
  formatUnits(_borrowedBalance - _borrowedAdminFees, this.market.borrowed_token.decimals),
@@ -202,15 +214,8 @@ export class StatsBaseModule {
202
214
 
203
215
  public async statsTotalDebt(isGetter = true, useAPI = true): Promise<string> {
204
216
  if(useAPI) {
205
- const response = await _getMarketsData(this.llamalend.constants.NETWORK_NAME);
206
-
207
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase())
208
-
209
- if(market) {
210
- return market.borrowed.total.toString();
211
- } else {
212
- throw new Error('Market not found in API')
213
- }
217
+ const market = await this._fetchMarketDataFromAPI();
218
+ return market.borrowed.total.toString();
214
219
  } else {
215
220
  let _debt;
216
221
  if(isGetter) {
@@ -226,42 +231,32 @@ export class StatsBaseModule {
226
231
 
227
232
  public statsAmmBalances = async (isGetter = true, useAPI = false): Promise<{ borrowed: string, collateral: string }> => {
228
233
  if(useAPI) {
229
- const response = await _getMarketsData(this.llamalend.constants.NETWORK_NAME);
230
-
231
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase())
232
-
233
- if(market) {
234
- return {
235
- borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
236
- collateral: market.ammBalances.ammBalanceCollateral.toString(),
237
- }
238
- } else {
239
- throw new Error('Market not found in API')
240
- }
234
+ const market = await this._fetchMarketDataFromAPI();
235
+ return {
236
+ borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
237
+ collateral: market.ammBalances.ammBalanceCollateral.toString(),
238
+ };
241
239
  } else {
242
240
  const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
243
241
  const collateralContract = this.llamalend.contracts[this.market.addresses.collateral_token].multicallContract;
244
- const ammContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
245
242
 
246
- let _balance_x, _fee_x, _balance_y, _fee_y; // TODO: fees are always 0
243
+ let _balance_x: bigint, _balance_y: bigint;
244
+ let _fee_x: bigint, _fee_y: bigint;
245
+
247
246
  if(isGetter) {
248
- [_balance_x, _fee_x, _balance_y, _fee_y] = [
249
- cacheStats.get(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.amm)),
250
- cacheStats.get(cacheKey(this.market.addresses.amm, 'admin_fees_x')),
251
- cacheStats.get(cacheKey(this.market.addresses.collateral_token, 'balanceOf', this.market.addresses.amm)),
252
- cacheStats.get(cacheKey(this.market.addresses.amm, 'admin_fees_y')),
253
- ]
247
+ _balance_x = cacheStats.get(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.amm));
248
+ _balance_y = cacheStats.get(cacheKey(this.market.addresses.collateral_token, 'balanceOf', this.market.addresses.amm));
249
+ [_fee_x, _fee_y] = await this._getAdminFeesXY(true);
254
250
  } else {
255
- [_balance_x, _fee_x, _balance_y, _fee_y] = await this.llamalend.multicallProvider.all([
256
- borrowedContract.balanceOf(this.market.addresses.amm),
257
- ammContract.admin_fees_x(),
258
- collateralContract.balanceOf(this.market.addresses.amm),
259
- ammContract.admin_fees_y(),
260
- ]);
251
+ [[_balance_x, _balance_y], [_fee_x, _fee_y]] = await Promise.all([
252
+ this.llamalend.multicallProvider.all([
253
+ borrowedContract.balanceOf(this.market.addresses.amm),
254
+ collateralContract.balanceOf(this.market.addresses.amm),
255
+ ]),
256
+ this._getAdminFeesXY(false),
257
+ ]) as [[bigint, bigint], [bigint, bigint]];
261
258
  cacheStats.set(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.amm), _balance_x);
262
- cacheStats.set(cacheKey(this.market.addresses.amm, 'admin_fees_x'), _fee_x);
263
259
  cacheStats.set(cacheKey(this.market.addresses.collateral_token, 'balanceOf', this.market.addresses.amm), _balance_y);
264
- cacheStats.set(cacheKey(this.market.addresses.amm, 'admin_fees_y'), _fee_y);
265
260
  }
266
261
 
267
262
  return {
@@ -271,43 +266,49 @@ export class StatsBaseModule {
271
266
  }
272
267
  }
273
268
 
274
- public async statsCapAndAvailable(isGetter = true, useAPI = false): Promise<{ cap: string, available: string }> {
275
- if(useAPI) {
276
- const response = await _getMarketsData(this.llamalend.constants.NETWORK_NAME);
269
+ protected async _statsCapAndAvailableFromAPI(): Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }> {
270
+ const market = await this._fetchMarketDataFromAPI();
271
+ return {
272
+ totalAssets: market.totalSupplied.total.toString(),
273
+ borrowCap: Infinity.toString(),
274
+ available: market.availableToBorrow.total.toString(),
275
+ availableForBorrow: market.availableToBorrow.total.toString(),
276
+ };
277
+ }
277
278
 
278
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase())
279
+ protected async _statsCapAndAvailableOnChain(isGetter: boolean): Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }> {
280
+ const vaultContract = this.llamalend.contracts[this.market.addresses.vault].multicallContract;
281
+ const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
279
282
 
280
- if(market) {
281
- return {
282
- cap: market.totalSupplied.total.toString(),
283
- available: market.availableToBorrow.total.toString(),
284
- }
285
- } else {
286
- throw new Error('Market not found in API')
287
- }
283
+ let _cap, _available;
284
+ if(isGetter) {
285
+ _cap = cacheStats.get(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller));
286
+ _available = cacheStats.get(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.controller));
288
287
  } else {
289
- const vaultContract = this.llamalend.contracts[this.market.addresses.vault].multicallContract;
290
- const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
291
-
292
- let _cap, _available;
293
- if(isGetter) { // TODO: should call controller.available_balance() instead of borrowed_token.balanceOf(controller)
294
- _cap = cacheStats.get(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller));
295
- _available = cacheStats.get(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.controller));
296
- } else {
297
- [_cap, _available] =await this.llamalend.multicallProvider.all([
298
- vaultContract.totalAssets(this.market.addresses.controller),
299
- borrowedContract.balanceOf(this.market.addresses.controller),
300
- ]);
301
- cacheStats.set(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller), _cap);
302
- cacheStats.set(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.controller), _available);
303
- }
288
+ [_cap, _available] = await this.llamalend.multicallProvider.all([
289
+ vaultContract.totalAssets(this.market.addresses.controller),
290
+ borrowedContract.balanceOf(this.market.addresses.controller),
291
+ ]);
292
+ cacheStats.set(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller), _cap);
293
+ cacheStats.set(cacheKey(this.market.addresses.borrowed_token, 'balanceOf', this.market.addresses.controller), _available);
294
+ }
304
295
 
305
- return {
306
- cap: this.llamalend.formatUnits(_cap, this.market.borrowed_token.decimals),
307
- available: this.llamalend.formatUnits(_available, this.market.borrowed_token.decimals),
308
- }
309
- // cap -> totalAssets
310
- // add cap: controller.borrow_cap() // Display
296
+ const available = this.llamalend.formatUnits(_available, this.market.borrowed_token.decimals);
297
+ return {
298
+ totalAssets: this.llamalend.formatUnits(_cap, this.market.borrowed_token.decimals),
299
+ borrowCap: Infinity.toString(),
300
+ available,
301
+ availableForBorrow: available,
311
302
  }
312
303
  }
304
+
305
+ public async statsCapAndAvailable(isGetter = true, useAPI = false): Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }> {
306
+ if(useAPI) return this._statsCapAndAvailableFromAPI();
307
+ return this._statsCapAndAvailableOnChain(isGetter);
308
+ }
309
+
310
+ public statsAdminPercentage = async (): Promise<string> => {
311
+ const _adminPercentage = await this._fetchAdminPercentage();
312
+ return formatUnits(_adminPercentage * BigInt(100));
313
+ }
313
314
  }
@@ -289,10 +289,10 @@ export class VaultModule {
289
289
  }
290
290
 
291
291
  public async vaultTotalLiquidity(useAPI = true): Promise<string> {
292
- const { cap } = await this.market.stats.capAndAvailable(true, useAPI);
292
+ const { totalAssets } = await this.market.stats.capAndAvailable(true, useAPI);
293
293
  const price = await _getUsdRate.call(this.llamalend, this.market.addresses.borrowed_token);
294
294
 
295
- return BN(cap).times(price).toFixed(6)
295
+ return BN(totalAssets).times(price).toFixed(6)
296
296
  }
297
297
 
298
298
  private _calcCrvApr = async (futureWorkingSupplyBN: BigNumber | null = null): Promise<[baseApy: number, boostedApy: number]> => {
@@ -1,3 +1,61 @@
1
+ import memoize from "memoizee";
1
2
  import { StatsBaseModule } from "../common/statsBase.js";
3
+ import {cacheKey, cacheStats} from "../../../cache";
4
+ import BigNumber from "bignumber.js";
5
+ import {BN} from "../../../utils";
2
6
 
3
- export class StatsV2Module extends StatsBaseModule {}
7
+ export class StatsV2Module extends StatsBaseModule {
8
+ protected _fetchAdminPercentage = memoize(async (): Promise<bigint> => {
9
+ return await this.llamalend.contracts[this.market.addresses.controller].contract.admin_percentage(this.llamalend.constantOptions);
10
+ }, {
11
+ promise: true,
12
+ maxAge: 30 * 60 * 1000, // 30m
13
+ });
14
+
15
+ protected _fetchAdminFee = async (): Promise<bigint> => BigInt(0);
16
+
17
+ protected _getAdminFeesXY = async (): Promise<[bigint, bigint]> => [BigInt(0), BigInt(0)];
18
+
19
+ protected async _statsCapAndAvailableFromAPI() {
20
+ const market = await this._fetchMarketDataFromAPI();
21
+ return {
22
+ totalAssets: market.totalSupplied.total.toString(),
23
+ borrowCap: market.borrowCap.total.toString(),
24
+ available: market.availableToBorrow.total.toString(),
25
+ availableForBorrow: market.availableToBorrow.total.toString(),
26
+ };
27
+ }
28
+
29
+ protected async _statsCapAndAvailableOnChain(isGetter: boolean): Promise<{ borrowCap: string, available: string, totalAssets: string, availableForBorrow: string }> {
30
+ const vaultContract = this.llamalend.contracts[this.market.addresses.vault].multicallContract;
31
+ const controllerContract = this.llamalend.contracts[this.market.addresses.controller].multicallContract;
32
+
33
+ let _cap: bigint, _available: bigint, _totalAssets: bigint, _totalDebt: bigint;
34
+ if(isGetter) {
35
+ _totalAssets = cacheStats.get(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller));
36
+ _cap = cacheStats.get(cacheKey(this.market.addresses.controller, 'borrow_cap'));
37
+ _available = cacheStats.get(cacheKey(this.market.addresses.controller, 'available_balance'));
38
+ _totalDebt = cacheStats.get(cacheKey(this.market.addresses.controller, 'total_debt'));
39
+ } else {
40
+ [_totalAssets, _available, _cap, _totalDebt] = await this.llamalend.multicallProvider.all([
41
+ vaultContract.totalAssets(this.market.addresses.controller),
42
+ controllerContract.available_balance(),
43
+ controllerContract.borrow_cap(),
44
+ controllerContract.total_debt(),
45
+ ]);
46
+
47
+ cacheStats.set(cacheKey(this.market.addresses.vault, 'totalAssets', this.market.addresses.controller), _totalAssets);
48
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'borrow_cap'), _cap);
49
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'available_balance'), _available);
50
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'total_debt'), _totalDebt);
51
+ }
52
+
53
+ const totalAssets = this.llamalend.formatUnits(_totalAssets, this.market.borrowed_token.decimals);
54
+ const borrowCap = this.llamalend.formatUnits(_cap, this.market.borrowed_token.decimals);
55
+ const available = this.llamalend.formatUnits(_available, this.market.borrowed_token.decimals);
56
+ const totalDebt = this.llamalend.formatUnits(_totalDebt, this.market.borrowed_token.decimals);
57
+ const availableForBorrow = BigNumber.min(BN(available), BN(borrowCap).minus(BN(totalDebt))).toFixed();
58
+
59
+ return { totalAssets, borrowCap, available, availableForBorrow }
60
+ }
61
+ }
@@ -0,0 +1,46 @@
1
+ import { BN, toBN } from "../utils";
2
+ import { INetworkName, IMarketData, IMarketDataAPI } from "../interfaces";
3
+
4
+ export type RatesResult = {
5
+ borrowApr: string;
6
+ lendApr: string;
7
+ borrowApy: string;
8
+ lendApy: string;
9
+ };
10
+
11
+ /**
12
+ * Computes borrow/lend APR and APY from a raw per-second rate and current debt/cap.
13
+ * borrowApy = e^(rate * 365 * 86400) - 1
14
+ * lendApy = (debt * e^(rate * 365 * 86400) - debt) / cap
15
+ */
16
+ export const computeRatesFromRate = (
17
+ _rate: bigint,
18
+ debt: string | number,
19
+ cap: string | number
20
+ ): RatesResult => {
21
+ const annualFactor = toBN(_rate).times(365).times(86400);
22
+ const expFactor = Math.E ** annualFactor.toNumber();
23
+
24
+ const borrowApr = annualFactor.times(100).toString();
25
+ const borrowApy = String((expFactor - 1) * 100);
26
+
27
+ const lendAprRaw = annualFactor.times(debt).div(cap).times(100);
28
+ const lendApr = lendAprRaw.isNaN() ? "0" : lendAprRaw.toString();
29
+ const lendApyRaw = BN(debt).times(expFactor).minus(debt).div(cap).times(100);
30
+ const lendApy = lendApyRaw.isNaN() ? "0" : lendApyRaw.toString();
31
+
32
+ return { borrowApr, lendApr, borrowApy, lendApy };
33
+ }
34
+
35
+ export const fetchMarketDataByVault = async (
36
+ networkName: INetworkName,
37
+ vaultAddress: string,
38
+ getData: (network: INetworkName) => Promise<IMarketData>
39
+ ): Promise<IMarketDataAPI> => {
40
+ const response = await getData(networkName);
41
+ const market = response.lendingVaultData.find(
42
+ (item) => item.address.toLowerCase() === vaultAddress.toLowerCase()
43
+ );
44
+ if (!market) throw new Error("Market not found in API");
45
+ return market;
46
+ }