@raac/rpc 1.2.0-beta.13 → 1.2.0-beta.14

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.
@@ -1,14 +1,38 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const ethers_1 = require("ethers");
7
+ const chains_1 = __importDefault(require("../../configs/chains"));
4
8
  const _helpers_1 = require("./_helpers");
5
9
  const artifacts_1 = require("../../utils/artifacts");
10
+ const getContractAddress_1 = __importDefault(require("../../contracts/getContractAddress"));
11
+ const RAY = 10n ** 27n;
12
+ const HALF_RAY = RAY / 2n;
13
+ /** Mirrors WadRayMath.rayMul (half-up rounding). */
14
+ const rayMul = (a, b) => (a * b + HALF_RAY) / RAY;
15
+ /** A RAY-denominated annual rate as a percentage string, e.g. 5e25 -> "5.00". */
16
+ const asPercent = (rate) => (Number(rate) / 1e25).toFixed(2);
17
+ /** A RAY-denominated value as a unit decimal string, e.g. RAY -> "1.000000". */
18
+ const asDecimal = (value) => (Number(value) / 1e27).toFixed(6);
19
+ /**
20
+ * Effective yearly cost of a nominal RAY borrow rate: e^r - 1.
21
+ * The usage index compounds (ReserveLibrary.calculateCompoundedInterest), so the
22
+ * borrow side's effective rate exceeds its nominal rate. The liquidity index
23
+ * accrues linearly, so the supply side has no equivalent gap.
24
+ */
25
+ const toCompoundedApy = (rate) => Math.expm1(Number(rate) / 1e27);
6
26
  async function getLendingPoolInfo(chainId, userAddress, provider) {
7
27
  try {
28
+ if (!provider) {
29
+ const rpc = chains_1.default[chainId].rpcs[0];
30
+ provider = new ethers_1.ethers.JsonRpcProvider(rpc);
31
+ }
8
32
  const lendingPoolContract = await (0, _helpers_1.getLendingPoolContract)(chainId, provider);
9
33
  const handleError = (operation, returnType) => (error) => {
10
34
  if (error.code === "BAD_DATA") {
11
- console.log(`BAD_DATA from ${operation} - Pool empty?`, error.messag);
35
+ console.log(`BAD_DATA from ${operation} - Pool empty?`, error.message);
12
36
  return returnType || null;
13
37
  }
14
38
  throw error;
@@ -19,85 +43,93 @@ async function getLendingPoolInfo(chainId, userAddress, provider) {
19
43
  lendingPoolContract.getNormalizedIncome().catch(handleError("getNormalizedIncome", 0n)),
20
44
  lendingPoolContract.getNormalizedDebt().catch(handleError("getNormalizedDebt", 0n)),
21
45
  ]);
22
- // Extract data from reserve
46
+ // Reserve state
23
47
  const totalLiquidity = reserve?.totalLiquidity ?? 0n;
24
48
  const totalUsage = reserve?.totalUsage ?? 0n;
25
49
  const liquidityIndex = reserve?.liquidityIndex ?? 0n;
26
50
  const usageIndex = reserve?.usageIndex ?? 0n;
27
- // Compute utilization
28
- let utilization = 0n;
29
- if (totalLiquidity + totalUsage > 0n) {
30
- utilization = (totalUsage * BigInt(1e27)) / (totalLiquidity + totalUsage);
31
- }
32
- // Extract rate data
33
- const currentLiquidityRate = rateData?.currentLiquidityRate ?? 0n;
34
- const currentUsageRate = rateData?.currentUsageRate ?? 0n;
35
- // why is prime rate 0?
51
+ // Rates as maintained by the pool. These are written by
52
+ // ReserveLibrary.updateInterestRatesAndLiquidity on every operation that moves
53
+ // liquidity or debt, and interest accrues at them until the next one, so they
54
+ // are the rates actually in effect - not a stale snapshot to be recomputed.
55
+ const supplyRate = rateData?.currentLiquidityRate ?? 0n;
56
+ const borrowRate = rateData?.currentUsageRate ?? 0n;
57
+ const utilization = rateData?.currentUtilizationRate ?? 0n;
58
+ const currentProtocolFeeRate = rateData?.currentProtocolFeeRate ?? 0n;
59
+ // Rate curve parameters
36
60
  const primeRate = rateData?.primeRate ?? 0n;
37
61
  const baseRate = rateData?.baseRate ?? 0n;
38
62
  const optimalRate = rateData?.optimalRate ?? 0n;
39
63
  const maxRate = rateData?.maxRate ?? 0n;
40
64
  const optimalUtilizationRate = rateData?.optimalUtilizationRate ?? 0n;
41
65
  const protocolFeeRate = rateData?.protocolFeeRate ?? 0n;
42
- const pendingProtocolFeeAmount = reserve?.[8];
43
- // Compute APY
44
- let apr;
45
- if (utilization <= optimalUtilizationRate && optimalUtilizationRate > 0) {
46
- apr = optimalRate;
47
- }
48
- else {
49
- const excessUtilization = utilization - optimalUtilizationRate;
50
- const maxExcessUtilization = BigInt(1e27) - optimalUtilizationRate;
51
- const rateSlope = maxRate - optimalRate;
52
- const rateIncrease = (excessUtilization * rateSlope) / maxExcessUtilization;
53
- apr = primeRate + rateIncrease;
54
- apr = apr > maxRate ? maxRate : apr;
55
- }
56
- // compute the APY
57
- // FIXME: This is not correct
58
- let apy;
59
- const grossLiquidityRate = utilization * currentUsageRate / BigInt(1e27);
60
- const protocolFeeAmount = grossLiquidityRate * protocolFeeRate / BigInt(1e27);
61
- apy = grossLiquidityRate - protocolFeeAmount;
62
- console.log("apr", apy);
66
+ const pendingProtocolFeeAmount = await getPendingProtocolFee(chainId, provider, reserve, normalizedIncome);
63
67
  let userRedeemable = 0n;
64
68
  if (userAddress) {
65
- // Fetch user's scaled RToken balance
69
+ // RToken.balanceOf already applies the liquidity index, so it is the
70
+ // redeemable underlying amount - do not scale it again.
66
71
  const rTokenAddress = reserve?.reserveRTokenAddress;
67
72
  const rTokenABI = (0, artifacts_1.getABI)("rtoken");
68
73
  const rTokenContract = new ethers_1.ethers.Contract(rTokenAddress, rTokenABI, provider);
69
- const userScaledBalance = await rTokenContract
70
- .balanceOf(userAddress)
71
- .catch(handleError("rToken balanceOf"));
72
- // Compute user's redeemable balance: userRedeemable = userScaledBalance * liquidityIndex / RAY
73
- userRedeemable = (userScaledBalance * liquidityIndex) / BigInt(1e27);
74
+ userRedeemable =
75
+ (await rTokenContract
76
+ .balanceOf(userAddress)
77
+ .catch(handleError("rToken balanceOf", 0n))) ?? 0n;
74
78
  }
75
- const result = {
79
+ return {
76
80
  totalLiquidity: ethers_1.ethers.formatEther(totalLiquidity),
77
81
  totalBorrow: ethers_1.ethers.formatEther(totalUsage),
78
- utilization: (Number(utilization.toString()) / 1e27).toFixed(6), // Utilization as a decimal value between 0 and 1
79
- liquidityIndex: (Number(liquidityIndex.toString()) / 1e27).toFixed(6),
80
- usageIndex: (Number(usageIndex.toString()) / 1e27).toFixed(6),
81
- currentLiquidityRate: (Number(currentLiquidityRate.toString()) / 1e25).toFixed(2),
82
- currentUsageRate: (Number(currentUsageRate.toString()) / 1e25).toFixed(2),
83
- primeRate: (Number(primeRate.toString()) / 1e25).toFixed(2),
84
- baseRate: (Number(baseRate.toString()) / 1e25).toFixed(2),
85
- optimalRate: (Number(optimalRate.toString()) / 1e25).toFixed(2),
86
- maxRate: (Number(maxRate.toString()) / 1e25).toFixed(2),
87
- optimalUtilizationRate: (Number(optimalUtilizationRate.toString()) / 1e27).toFixed(6),
88
- protocolFeeRate: (Number(protocolFeeRate.toString()) / 1e25).toFixed(2),
89
- normalizedIncome: (Number(normalizedIncome.toString()) / 1e27).toFixed(6),
90
- normalizedDebt: (Number(normalizedDebt.toString()) / 1e27).toFixed(6),
91
- apy: (Number(apy.toString()) / 1e25).toFixed(2),
92
- userRedeemable: ethers_1.ethers.formatEther(userRedeemable ?? 0n),
93
- apr: (Number(apr.toString()) / 1e25).toFixed(2),
82
+ utilization: asDecimal(utilization),
83
+ liquidityIndex: asDecimal(liquidityIndex),
84
+ usageIndex: asDecimal(usageIndex),
85
+ supplyRate: asPercent(supplyRate),
86
+ borrowRate: asPercent(borrowRate),
87
+ supplyApy: asPercent(supplyRate),
88
+ borrowApy: (toCompoundedApy(borrowRate) * 100).toFixed(2),
89
+ primeRate: asPercent(primeRate),
90
+ baseRate: asPercent(baseRate),
91
+ optimalRate: asPercent(optimalRate),
92
+ maxRate: asPercent(maxRate),
93
+ optimalUtilizationRate: asDecimal(optimalUtilizationRate),
94
+ protocolFeeRate: asPercent(protocolFeeRate),
95
+ currentProtocolFeeRate: asPercent(currentProtocolFeeRate),
96
+ normalizedIncome: asDecimal(normalizedIncome),
97
+ normalizedDebt: asDecimal(normalizedDebt),
98
+ userRedeemable: ethers_1.ethers.formatEther(userRedeemable),
94
99
  pendingProtocolFeeAmount: ethers_1.ethers.formatEther(pendingProtocolFeeAmount),
100
+ /** @deprecated use `supplyRate` */
101
+ currentLiquidityRate: asPercent(supplyRate),
102
+ /** @deprecated use `borrowRate` */
103
+ currentUsageRate: asPercent(borrowRate),
104
+ /** @deprecated use `borrowRate` */
105
+ apr: asPercent(borrowRate),
106
+ /** @deprecated use `supplyRate` */
107
+ apy: asPercent(supplyRate),
95
108
  };
96
- return result;
97
109
  }
98
110
  catch (error) {
99
111
  console.error("Error fetching lending pool info:", error);
100
112
  throw error;
101
113
  }
102
114
  }
115
+ /**
116
+ * Resolves the protocol fee owed in underlying asset units.
117
+ *
118
+ * Newer pools bank the fee scaled by the liquidity index and expose
119
+ * `getPendingProtocolFee()`, which re-values it at the current index. Older pools
120
+ * store the unscaled amount in the reserve's ninth slot and have no getter, so
121
+ * fall back to reading it directly.
122
+ */
123
+ async function getPendingProtocolFee(chainId, provider, reserve, normalizedIncome) {
124
+ const stored = reserve?.[8] ?? 0n;
125
+ if (stored === 0n)
126
+ return 0n;
127
+ try {
128
+ const pool = new ethers_1.ethers.Contract((0, getContractAddress_1.default)(chainId, "lendingpool"), ["function getPendingProtocolFee() view returns (uint256)"], provider);
129
+ return await pool.getPendingProtocolFee();
130
+ }
131
+ catch {
132
+ return normalizedIncome > 0n ? rayMul(stored, normalizedIncome) : stored;
133
+ }
134
+ }
103
135
  exports.default = getLendingPoolInfo;
@@ -1,24 +1,33 @@
1
1
  import { Provider } from "ethers";
2
2
  import { ChainId } from "../../configs/chains";
3
- declare function getLendingPoolInfo(chainId: ChainId, userAddress: string, provider: Provider): Promise<{
3
+ declare function getLendingPoolInfo(chainId: ChainId, userAddress?: string, provider?: Provider): Promise<{
4
4
  totalLiquidity: string;
5
5
  totalBorrow: string;
6
6
  utilization: string;
7
7
  liquidityIndex: string;
8
8
  usageIndex: string;
9
- currentLiquidityRate: string;
10
- currentUsageRate: string;
9
+ supplyRate: string;
10
+ borrowRate: string;
11
+ supplyApy: string;
12
+ borrowApy: string;
11
13
  primeRate: string;
12
14
  baseRate: string;
13
15
  optimalRate: string;
14
16
  maxRate: string;
15
17
  optimalUtilizationRate: string;
16
18
  protocolFeeRate: string;
19
+ currentProtocolFeeRate: string;
17
20
  normalizedIncome: string;
18
21
  normalizedDebt: string;
19
- apy: string;
20
22
  userRedeemable: string;
21
- apr: string;
22
23
  pendingProtocolFeeAmount: string;
24
+ /** @deprecated use `supplyRate` */
25
+ currentLiquidityRate: string;
26
+ /** @deprecated use `borrowRate` */
27
+ currentUsageRate: string;
28
+ /** @deprecated use `borrowRate` */
29
+ apr: string;
30
+ /** @deprecated use `supplyRate` */
31
+ apy: string;
23
32
  }>;
24
33
  export default getLendingPoolInfo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raac/rpc",
3
- "version": "1.2.0-beta.13",
3
+ "version": "1.2.0-beta.14",
4
4
  "description": "RPC Library for RAAC",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/types/index.d.ts",