@curvefi/llamalend-api 2.0.3 → 2.0.4

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.
@@ -143,6 +143,12 @@ This document tracks feature support across market versions.
143
143
  | ammBalances() | ✅ | ✅ | ✅ | ✅ | ✅ |
144
144
  | capAndAvailable() | ✅ | ✅ | ❌ | ✅ | ❌ |
145
145
 
146
+ ## Stats Module (`market.stats`) new methods
147
+ | Method | v1 | v2 | Same logic | Same params | Same type |
148
+ |--------|----|----|-----------------|----------------------|-----------------------|
149
+ | adminPercentage() | ✅ | ✅ | ✅ | ✅ | ✅ |
150
+
151
+
146
152
  ---
147
153
 
148
154
  ## Wallet Module (`market.wallet`)
@@ -68,16 +68,17 @@ export class LendMarketTemplate {
68
68
  getCurrentLeverageParams: userPosition.getCurrentLeverageParams.bind(userPosition),
69
69
  };
70
70
  this.stats = {
71
- parameters: stats.statsParameters.bind(this),
72
- rates: stats.statsRates.bind(this),
73
- futureRates: stats.statsFutureRates.bind(this),
74
- balances: stats.statsBalances.bind(this),
75
- bandsInfo: stats.statsBandsInfo.bind(this),
76
- bandBalances: stats.statsBandBalances.bind(this),
77
- bandsBalances: stats.statsBandsBalances.bind(this),
78
- totalDebt: stats.statsTotalDebt.bind(this),
79
- ammBalances: stats.statsAmmBalances.bind(this),
80
- capAndAvailable: stats.statsCapAndAvailable.bind(this),
71
+ parameters: stats.statsParameters.bind(stats),
72
+ rates: stats.statsRates.bind(stats),
73
+ futureRates: stats.statsFutureRates.bind(stats),
74
+ balances: stats.statsBalances.bind(stats),
75
+ bandsInfo: stats.statsBandsInfo.bind(stats),
76
+ bandBalances: stats.statsBandBalances.bind(stats),
77
+ bandsBalances: stats.statsBandsBalances.bind(stats),
78
+ totalDebt: stats.statsTotalDebt.bind(stats),
79
+ ammBalances: stats.statsAmmBalances.bind(stats),
80
+ capAndAvailable: stats.statsCapAndAvailable.bind(stats),
81
+ adminPercentage: stats.statsAdminPercentage.bind(stats),
81
82
  };
82
83
  this.wallet = {
83
84
  balances: wallet.balances.bind(this),
@@ -46,4 +46,5 @@ export interface IStatsV1 {
46
46
  cap: string;
47
47
  available: string;
48
48
  }>;
49
+ adminPercentage: () => Promise<string>;
49
50
  }
@@ -46,4 +46,5 @@ export interface IStatsV2 {
46
46
  cap: string;
47
47
  available: string;
48
48
  }>;
49
+ adminPercentage: () => Promise<string>;
49
50
  }
@@ -6,6 +6,9 @@ export declare class StatsBaseModule {
6
6
  protected market: LendMarketTemplate;
7
7
  protected llamalend: Llamalend;
8
8
  constructor(market: LendMarketTemplate);
9
+ protected _fetchAdminPercentage: () => Promise<bigint>;
10
+ private _getRate;
11
+ private _getFutureRate;
9
12
  statsParameters: (() => Promise<{
10
13
  fee: string;
11
14
  admin_fee: string;
@@ -21,8 +24,6 @@ export declare class StatsBaseModule {
21
24
  base_price: string;
22
25
  A: string;
23
26
  }>>;
24
- private _getRate;
25
- private _getFutureRate;
26
27
  statsRates(isGetter?: boolean, useAPI?: boolean): Promise<{
27
28
  borrowApr: string;
28
29
  lendApr: string;
@@ -66,4 +67,5 @@ export declare class StatsBaseModule {
66
67
  cap: string;
67
68
  available: string;
68
69
  }>;
70
+ statsAdminPercentage: () => Promise<string>;
69
71
  }
@@ -8,11 +8,39 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import memoize from "memoizee";
11
- import { parseUnits, BN, toBN, formatUnits, formatNumber, } from "../../../utils";
11
+ import { parseUnits, toBN, formatUnits, formatNumber, } from "../../../utils";
12
12
  import { _getMarketsData } from "../../../external-api";
13
13
  import { cacheKey, cacheStats } from "../../../cache";
14
+ import { computeRatesFromRate, fetchMarketDataByVault } from "../../utils";
15
+ const PRECISION = BigInt("1000000000000000000"); // 1e18
14
16
  export class StatsBaseModule {
15
17
  constructor(market) {
18
+ this._fetchAdminPercentage = () => __awaiter(this, void 0, void 0, function* () {
19
+ return BigInt(0);
20
+ });
21
+ this._getRate = (...args_1) => __awaiter(this, [...args_1], void 0, function* (isGetter = true) {
22
+ if (isGetter) {
23
+ const _rate = cacheStats.get(cacheKey(this.market.addresses.amm, 'rate'));
24
+ const _adminPercentage = yield this._fetchAdminPercentage();
25
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
26
+ }
27
+ else {
28
+ const [_rate, _adminPercentage] = yield Promise.all([
29
+ this.llamalend.contracts[this.market.addresses.amm].contract.rate(this.llamalend.constantOptions),
30
+ this._fetchAdminPercentage(),
31
+ ]);
32
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'rate'), _rate);
33
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
34
+ }
35
+ });
36
+ this._getFutureRate = (_dReserves, _dDebt) => __awaiter(this, void 0, void 0, function* () {
37
+ const mpContract = this.llamalend.contracts[this.market.addresses.monetary_policy].contract;
38
+ const [_rate, _adminPercentage] = yield Promise.all([
39
+ mpContract.future_rate(this.market.addresses.controller, _dReserves, _dDebt),
40
+ this._fetchAdminPercentage(),
41
+ ]);
42
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
43
+ });
16
44
  this.statsParameters = memoize(() => __awaiter(this, void 0, void 0, function* () {
17
45
  const llammaContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
18
46
  const controllerContract = this.llamalend.contracts[this.market.addresses.controller].multicallContract;
@@ -34,21 +62,6 @@ export class StatsBaseModule {
34
62
  promise: true,
35
63
  maxAge: 5 * 60 * 1000, // 5m
36
64
  });
37
- this._getRate = (...args_1) => __awaiter(this, [...args_1], void 0, function* (isGetter = true) {
38
- let _rate;
39
- if (isGetter) {
40
- _rate = cacheStats.get(cacheKey(this.market.addresses.amm, 'rate'));
41
- }
42
- else {
43
- _rate = yield this.llamalend.contracts[this.market.addresses.amm].contract.rate(this.llamalend.constantOptions);
44
- cacheStats.set(cacheKey(this.market.addresses.controller, 'rate'), _rate);
45
- }
46
- return _rate;
47
- });
48
- this._getFutureRate = (_dReserves, _dDebt) => __awaiter(this, void 0, void 0, function* () {
49
- const mpContract = this.llamalend.contracts[this.market.addresses.monetary_policy].contract;
50
- return yield mpContract.future_rate(this.market.addresses.controller, _dReserves, _dDebt);
51
- });
52
65
  this.statsBandsInfo = memoize(() => __awaiter(this, void 0, void 0, function* () {
53
66
  const ammContract = this.llamalend.contracts[this.market.addresses.amm].multicallContract;
54
67
  const calls = [
@@ -68,17 +81,11 @@ export class StatsBaseModule {
68
81
  });
69
82
  this.statsAmmBalances = (...args_1) => __awaiter(this, [...args_1], void 0, function* (isGetter = true, useAPI = false) {
70
83
  if (useAPI) {
71
- const response = yield _getMarketsData(this.llamalend.constants.NETWORK_NAME);
72
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase());
73
- if (market) {
74
- return {
75
- borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
76
- collateral: market.ammBalances.ammBalanceCollateral.toString(),
77
- };
78
- }
79
- else {
80
- throw new Error('Market not found in API');
81
- }
84
+ const market = yield fetchMarketDataByVault(this.llamalend.constants.NETWORK_NAME, this.market.addresses.vault, _getMarketsData);
85
+ return {
86
+ borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
87
+ collateral: market.ammBalances.ammBalanceCollateral.toString(),
88
+ };
82
89
  }
83
90
  else {
84
91
  const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
@@ -111,42 +118,29 @@ export class StatsBaseModule {
111
118
  };
112
119
  }
113
120
  });
121
+ this.statsAdminPercentage = () => __awaiter(this, void 0, void 0, function* () {
122
+ const _adminPercentage = yield this._fetchAdminPercentage();
123
+ return formatUnits(_adminPercentage * BigInt(100));
124
+ });
114
125
  this.market = market;
115
126
  this.llamalend = market.getLlamalend();
116
127
  }
117
128
  statsRates() {
118
129
  return __awaiter(this, arguments, void 0, function* (isGetter = true, useAPI = false) {
119
130
  if (useAPI) {
120
- const response = yield _getMarketsData(this.llamalend.constants.NETWORK_NAME);
121
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase());
122
- if (market) {
123
- return {
124
- borrowApr: (market.rates.borrowApr * 100).toString(),
125
- lendApr: (market.rates.lendApr * 100).toString(),
126
- borrowApy: (market.rates.borrowApy * 100).toString(),
127
- lendApy: (market.rates.lendApy * 100).toString(),
128
- };
129
- }
130
- else {
131
- throw new Error('Market not found in API');
132
- }
131
+ const market = yield fetchMarketDataByVault(this.llamalend.constants.NETWORK_NAME, this.market.addresses.vault, _getMarketsData);
132
+ return {
133
+ borrowApr: (market.rates.borrowApr * 100).toString(),
134
+ lendApr: (market.rates.lendApr * 100).toString(),
135
+ borrowApy: (market.rates.borrowApy * 100).toString(),
136
+ lendApy: (market.rates.lendApy * 100).toString(),
137
+ };
133
138
  }
134
139
  else {
135
140
  const _rate = yield this._getRate(isGetter);
136
- const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
137
- // borrowApy = e**(rate*365*86400) - 1
138
- const borrowApy = String(((Math.pow(2.718281828459, (toBN(_rate).times(365).times(86400)).toNumber())) - 1) * 100);
139
- let lendApr = "0";
140
- let lendApy = "0";
141
141
  const debt = yield this.statsTotalDebt(isGetter);
142
- if (Number(debt) > 0) {
143
- const { cap } = yield this.statsCapAndAvailable(isGetter);
144
- lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
145
- // lendApy = (debt * e**(rate*365*86400) - debt) / cap
146
- const debtInAYearBN = BN(debt).times(Math.pow(2.718281828459, (toBN(_rate).times(365).times(86400)).toNumber()));
147
- lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
148
- }
149
- return { borrowApr, lendApr, borrowApy, lendApy };
142
+ const { cap } = Number(debt) > 0 ? yield this.statsCapAndAvailable(isGetter) : { cap: "0" };
143
+ return computeRatesFromRate(_rate, debt, cap);
150
144
  }
151
145
  });
152
146
  }
@@ -155,20 +149,9 @@ export class StatsBaseModule {
155
149
  const _dReserves = parseUnits(dReserves, this.market.borrowed_token.decimals);
156
150
  const _dDebt = parseUnits(dDebt, this.market.borrowed_token.decimals);
157
151
  const _rate = yield this._getFutureRate(_dReserves, _dDebt);
158
- const borrowApr = toBN(_rate).times(365).times(86400).times(100).toString();
159
- // borrowApy = e**(rate*365*86400) - 1
160
- const borrowApy = String(((Math.pow(2.718281828459, (toBN(_rate).times(365).times(86400)).toNumber())) - 1) * 100);
161
- let lendApr = "0";
162
- let lendApy = "0";
163
152
  const debt = Number(yield this.statsTotalDebt()) + Number(dDebt);
164
- if (Number(debt) > 0) {
165
- const cap = Number((yield this.statsCapAndAvailable(true, useAPI)).cap) + Number(dReserves);
166
- lendApr = toBN(_rate).times(365).times(86400).times(debt).div(cap).times(100).toString();
167
- // lendApy = (debt * e**(rate*365*86400) - debt) / cap
168
- const debtInAYearBN = BN(debt).times(Math.pow(2.718281828459, (toBN(_rate).times(365).times(86400)).toNumber()));
169
- lendApy = debtInAYearBN.minus(debt).div(cap).times(100).toString();
170
- }
171
- return { borrowApr, lendApr, borrowApy, lendApy };
153
+ const cap = Number((yield this.statsCapAndAvailable(true, useAPI)).cap) + Number(dReserves);
154
+ return computeRatesFromRate(_rate, debt, cap);
172
155
  });
173
156
  }
174
157
  statsBalances() {
@@ -226,14 +209,8 @@ export class StatsBaseModule {
226
209
  statsTotalDebt() {
227
210
  return __awaiter(this, arguments, void 0, function* (isGetter = true, useAPI = true) {
228
211
  if (useAPI) {
229
- const response = yield _getMarketsData(this.llamalend.constants.NETWORK_NAME);
230
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase());
231
- if (market) {
232
- return market.borrowed.total.toString();
233
- }
234
- else {
235
- throw new Error('Market not found in API');
236
- }
212
+ const market = yield fetchMarketDataByVault(this.llamalend.constants.NETWORK_NAME, this.market.addresses.vault, _getMarketsData);
213
+ return market.borrowed.total.toString();
237
214
  }
238
215
  else {
239
216
  let _debt;
@@ -251,17 +228,11 @@ export class StatsBaseModule {
251
228
  statsCapAndAvailable() {
252
229
  return __awaiter(this, arguments, void 0, function* (isGetter = true, useAPI = false) {
253
230
  if (useAPI) {
254
- const response = yield _getMarketsData(this.llamalend.constants.NETWORK_NAME);
255
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase());
256
- if (market) {
257
- return {
258
- cap: market.totalSupplied.total.toString(),
259
- available: market.availableToBorrow.total.toString(),
260
- };
261
- }
262
- else {
263
- throw new Error('Market not found in API');
264
- }
231
+ const market = yield fetchMarketDataByVault(this.llamalend.constants.NETWORK_NAME, this.market.addresses.vault, _getMarketsData);
232
+ return {
233
+ cap: market.totalSupplied.total.toString(),
234
+ available: market.availableToBorrow.total.toString(),
235
+ };
265
236
  }
266
237
  else {
267
238
  const vaultContract = this.llamalend.contracts[this.market.addresses.vault].multicallContract;
@@ -1,3 +1,5 @@
1
+ import memoize from "memoizee";
1
2
  import { StatsBaseModule } from "../common/statsBase.js";
2
3
  export declare class StatsV2Module extends StatsBaseModule {
4
+ protected _fetchAdminPercentage: (() => Promise<bigint>) & memoize.Memoized<() => Promise<bigint>>;
3
5
  }
@@ -1,3 +1,22 @@
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 memoize from "memoizee";
1
11
  import { StatsBaseModule } from "../common/statsBase.js";
2
12
  export class StatsV2Module extends StatsBaseModule {
13
+ constructor() {
14
+ super(...arguments);
15
+ this._fetchAdminPercentage = memoize(() => __awaiter(this, void 0, void 0, function* () {
16
+ return yield this.llamalend.contracts[this.market.addresses.controller].contract.admin_percentage(this.llamalend.constantOptions);
17
+ }), {
18
+ promise: true,
19
+ maxAge: 30 * 60 * 1000, // 30m
20
+ });
21
+ }
3
22
  }
@@ -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,31 @@
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 lendApr = annualFactor.times(debt).div(cap).times(100).toString();
22
+ const lendApy = BN(debt).times(expFactor).minus(debt).div(cap).times(100).toString();
23
+ return { borrowApr, lendApr, borrowApy, lendApy };
24
+ };
25
+ export const fetchMarketDataByVault = (networkName, vaultAddress, getData) => __awaiter(void 0, void 0, void 0, function* () {
26
+ const response = yield getData(networkName);
27
+ const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === vaultAddress.toLowerCase());
28
+ if (!market)
29
+ throw new Error("Market not found in API");
30
+ return market;
31
+ });
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.4",
4
4
  "description": "JavaScript library for Curve Lending",
5
5
  "main": "lib/index.js",
6
6
  "author": "Macket",
@@ -166,16 +166,17 @@ 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 = {
@@ -18,4 +18,5 @@ export interface IStatsV1 {
18
18
  totalDebt: (isGetter?: boolean, useAPI?: boolean) => Promise<string>,
19
19
  ammBalances: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowed: string, collateral: string }>,
20
20
  capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ cap: string, available: string }>,
21
+ adminPercentage: () => Promise<string>,
21
22
  }
@@ -18,4 +18,5 @@ export interface IStatsV2 {
18
18
  totalDebt: (isGetter?: boolean, useAPI?: boolean) => Promise<string>,
19
19
  ammBalances: (isGetter?: boolean, useAPI?: boolean) => Promise<{ borrowed: string, collateral: string }>,
20
20
  capAndAvailable: (isGetter?: boolean, useAPI?: boolean) => Promise<{ cap: string, available: string }>,
21
+ adminPercentage: () => Promise<string>,
21
22
  }
@@ -3,7 +3,6 @@ import {TAmount} 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,34 @@ export class StatsBaseModule {
21
22
  this.llamalend = market.getLlamalend();
22
23
  }
23
24
 
25
+ protected _fetchAdminPercentage = async (): Promise<bigint> => {
26
+ return BigInt(0)
27
+ }
28
+
29
+ private _getRate = async (isGetter = true): Promise<bigint> => {
30
+ if (isGetter) {
31
+ const _rate: bigint = cacheStats.get(cacheKey(this.market.addresses.amm, 'rate'));
32
+ const _adminPercentage = await this._fetchAdminPercentage();
33
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
34
+ } else {
35
+ const [_rate, _adminPercentage] = await Promise.all([
36
+ this.llamalend.contracts[this.market.addresses.amm].contract.rate(this.llamalend.constantOptions),
37
+ this._fetchAdminPercentage(),
38
+ ]);
39
+ cacheStats.set(cacheKey(this.market.addresses.controller, 'rate'), _rate);
40
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
41
+ }
42
+ }
43
+
44
+ private _getFutureRate = async (_dReserves: bigint, _dDebt: bigint): Promise<bigint> => {
45
+ const mpContract = this.llamalend.contracts[this.market.addresses.monetary_policy].contract;
46
+ const [_rate, _adminPercentage] = await Promise.all([
47
+ mpContract.future_rate(this.market.addresses.controller, _dReserves, _dDebt),
48
+ this._fetchAdminPercentage(),
49
+ ]);
50
+ return _rate * (PRECISION - _adminPercentage) / PRECISION;
51
+ }
52
+
24
53
  public statsParameters = memoize(async (): Promise<{
25
54
  fee: string, // %
26
55
  admin_fee: string, // %
@@ -53,55 +82,24 @@ export class StatsBaseModule {
53
82
  maxAge: 5 * 60 * 1000, // 5m
54
83
  });
55
84
 
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
85
  public async statsRates(isGetter = true, useAPI = false): Promise<{borrowApr: string, lendApr: string, borrowApy: string, lendApy: string}> {
73
86
  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
- }
87
+ const market = await fetchMarketDataByVault(
88
+ this.llamalend.constants.NETWORK_NAME,
89
+ this.market.addresses.vault,
90
+ _getMarketsData
91
+ );
92
+ return {
93
+ borrowApr: (market.rates.borrowApr * 100).toString(),
94
+ lendApr: (market.rates.lendApr * 100).toString(),
95
+ borrowApy: (market.rates.borrowApy * 100).toString(),
96
+ lendApy: (market.rates.lendApy * 100).toString(),
97
+ };
88
98
  } else {
89
99
  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
100
  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 }
101
+ const { cap } = Number(debt) > 0 ? await this.statsCapAndAvailable(isGetter) : { cap: "0" };
102
+ return computeRatesFromRate(_rate, debt, cap);
105
103
  }
106
104
  }
107
105
 
@@ -109,21 +107,9 @@ export class StatsBaseModule {
109
107
  const _dReserves = parseUnits(dReserves, this.market.borrowed_token.decimals);
110
108
  const _dDebt = parseUnits(dDebt, this.market.borrowed_token.decimals);
111
109
  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
110
  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 }
111
+ const cap = Number((await this.statsCapAndAvailable(true, useAPI)).cap) + Number(dReserves);
112
+ return computeRatesFromRate(_rate, debt, cap);
127
113
  }
128
114
 
129
115
  public async statsBalances(): Promise<[string, string]> {
@@ -202,15 +188,12 @@ export class StatsBaseModule {
202
188
 
203
189
  public async statsTotalDebt(isGetter = true, useAPI = true): Promise<string> {
204
190
  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
- }
191
+ const market = await fetchMarketDataByVault(
192
+ this.llamalend.constants.NETWORK_NAME,
193
+ this.market.addresses.vault,
194
+ _getMarketsData
195
+ );
196
+ return market.borrowed.total.toString();
214
197
  } else {
215
198
  let _debt;
216
199
  if(isGetter) {
@@ -226,18 +209,15 @@ export class StatsBaseModule {
226
209
 
227
210
  public statsAmmBalances = async (isGetter = true, useAPI = false): Promise<{ borrowed: string, collateral: string }> => {
228
211
  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
- }
212
+ const market = await fetchMarketDataByVault(
213
+ this.llamalend.constants.NETWORK_NAME,
214
+ this.market.addresses.vault,
215
+ _getMarketsData
216
+ );
217
+ return {
218
+ borrowed: market.ammBalances.ammBalanceBorrowed.toString(),
219
+ collateral: market.ammBalances.ammBalanceCollateral.toString(),
220
+ };
241
221
  } else {
242
222
  const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
243
223
  const collateralContract = this.llamalend.contracts[this.market.addresses.collateral_token].multicallContract;
@@ -273,18 +253,15 @@ export class StatsBaseModule {
273
253
 
274
254
  public async statsCapAndAvailable(isGetter = true, useAPI = false): Promise<{ cap: string, available: string }> {
275
255
  if(useAPI) {
276
- const response = await _getMarketsData(this.llamalend.constants.NETWORK_NAME);
277
-
278
- const market = response.lendingVaultData.find((item) => item.address.toLowerCase() === this.market.addresses.vault.toLowerCase())
279
-
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
- }
256
+ const market = await fetchMarketDataByVault(
257
+ this.llamalend.constants.NETWORK_NAME,
258
+ this.market.addresses.vault,
259
+ _getMarketsData
260
+ );
261
+ return {
262
+ cap: market.totalSupplied.total.toString(),
263
+ available: market.availableToBorrow.total.toString(),
264
+ };
288
265
  } else {
289
266
  const vaultContract = this.llamalend.contracts[this.market.addresses.vault].multicallContract;
290
267
  const borrowedContract = this.llamalend.contracts[this.market.addresses.borrowed_token].multicallContract;
@@ -310,4 +287,9 @@ export class StatsBaseModule {
310
287
  // add cap: controller.borrow_cap() // Display
311
288
  }
312
289
  }
290
+
291
+ public statsAdminPercentage = async (): Promise<string> => {
292
+ const _adminPercentage = await this._fetchAdminPercentage();
293
+ return formatUnits(_adminPercentage * BigInt(100));
294
+ }
313
295
  }
@@ -1,3 +1,11 @@
1
+ import memoize from "memoizee";
1
2
  import { StatsBaseModule } from "../common/statsBase.js";
2
3
 
3
- export class StatsV2Module extends StatsBaseModule {}
4
+ export class StatsV2Module extends StatsBaseModule {
5
+ protected _fetchAdminPercentage = memoize(async (): Promise<bigint> => {
6
+ return await this.llamalend.contracts[this.market.addresses.controller].contract.admin_percentage(this.llamalend.constantOptions);
7
+ }, {
8
+ promise: true,
9
+ maxAge: 30 * 60 * 1000, // 30m
10
+ });
11
+ }
@@ -0,0 +1,44 @@
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 lendApr = annualFactor.times(debt).div(cap).times(100).toString();
28
+ const lendApy = BN(debt).times(expFactor).minus(debt).div(cap).times(100).toString();
29
+
30
+ return { borrowApr, lendApr, borrowApy, lendApy };
31
+ }
32
+
33
+ export const fetchMarketDataByVault = async (
34
+ networkName: INetworkName,
35
+ vaultAddress: string,
36
+ getData: (network: INetworkName) => Promise<IMarketData>
37
+ ): Promise<IMarketDataAPI> => {
38
+ const response = await getData(networkName);
39
+ const market = response.lendingVaultData.find(
40
+ (item) => item.address.toLowerCase() === vaultAddress.toLowerCase()
41
+ );
42
+ if (!market) throw new Error("Market not found in API");
43
+ return market;
44
+ }