diff --git a/src/__mocks__/contracts/prime.ts b/src/__mocks__/contracts/prime.ts new file mode 100644 index 0000000000..a3a2025491 --- /dev/null +++ b/src/__mocks__/contracts/prime.ts @@ -0,0 +1,27 @@ +import { BigNumber as BN } from 'ethers'; +import { Prime } from 'packages/contracts'; + +const primeResponses: { + MINIMUM_STAKED_XVS: Awaited>; + getAllMarkets: Awaited>; + calculateAPR: Awaited>; + estimateAPR: Awaited>; +} = { + MINIMUM_STAKED_XVS: BN.from('1000000000000000000000'), + getAllMarkets: [ + '0xD5C4C2e2facBEB59D0216D0595d63FcDc6F9A1a7', + '0xb7526572FFE56AB9D7489838Bf2E18e3323b441A', + '0x08e0A5575De71037aE36AbfAfb516595fE68e5e4', + '0x74469281310195A04840Daf6EdF576F559a3dE80', + ] as Awaited>, + calculateAPR: { + borrowAPR: BN.from('32'), + supplyAPR: BN.from('29'), + } as Awaited>, + estimateAPR: { + borrowAPR: BN.from('20'), + supplyAPR: BN.from('23'), + } as Awaited>, +}; + +export default primeResponses; diff --git a/src/__mocks__/models/asset.ts b/src/__mocks__/models/asset.ts index e0ce343b9b..747ef0013c 100644 --- a/src/__mocks__/models/asset.ts +++ b/src/__mocks__/models/asset.ts @@ -141,6 +141,11 @@ export const assetData: Asset[] = [ type: 'primeSimulation', token: xvs, apyPercentage: new BigNumber('1.753105649796123742'), + referenceValues: { + userBorrowBalanceTokens: new BigNumber('1000'), + userSupplyBalanceTokens: new BigNumber('1000'), + userXvsStakedTokens: new BigNumber('1000'), + }, }, ], borrowDistributions: [ @@ -154,6 +159,11 @@ export const assetData: Asset[] = [ type: 'primeSimulation', token: xvs, apyPercentage: new BigNumber('1.015310564979612374'), + referenceValues: { + userBorrowBalanceTokens: new BigNumber('1000'), + userSupplyBalanceTokens: new BigNumber('1000'), + userXvsStakedTokens: new BigNumber('1000'), + }, }, ], }, @@ -195,6 +205,11 @@ export const assetData: Asset[] = [ type: 'primeSimulation', token: xvs, apyPercentage: new BigNumber('1.753105649796123742'), + referenceValues: { + userBorrowBalanceTokens: new BigNumber('1000'), + userSupplyBalanceTokens: new BigNumber('1000'), + userXvsStakedTokens: new BigNumber('1000'), + }, }, ], borrowDistributions: [ diff --git a/src/clients/api/queries/getHypotheticalPrimeApys/index.ts b/src/clients/api/queries/getHypotheticalPrimeApys/index.ts index 8b0e1ec2b7..61d8177774 100644 --- a/src/clients/api/queries/getHypotheticalPrimeApys/index.ts +++ b/src/clients/api/queries/getHypotheticalPrimeApys/index.ts @@ -1,8 +1,6 @@ import BigNumber from 'bignumber.js'; import { Prime } from 'packages/contracts'; -import { calculateApy } from 'utilities'; - -import { DAYS_PER_YEAR } from 'constants/daysPerYear'; +import { convertAprToApy } from 'utilities'; export interface GetHypotheticalPrimeApysInput { primeContract: Prime; @@ -18,13 +16,6 @@ export interface GetHypotheticalPrimeApysOutput { borrowApyPercentage: BigNumber; } -const convertAprToApy = ({ aprBips }: { aprBips: string }) => { - // Convert bips to daily rate - const dailyRate = new BigNumber(aprBips).div(1000).div(DAYS_PER_YEAR); - // Convert daily rate to APY - return calculateApy({ dailyRate }); -}; - const getHypotheticalPrimeApys = async ({ primeContract, vTokenAddress, diff --git a/src/clients/api/queries/getIsolatedPools/__tests__/__snapshots__/index.spec.ts.snap b/src/clients/api/queries/getIsolatedPools/__tests__/__snapshots__/index.spec.ts.snap index 0ea1b7a842..73cbb830ad 100644 --- a/src/clients/api/queries/getIsolatedPools/__tests__/__snapshots__/index.spec.ts.snap +++ b/src/clients/api/queries/getIsolatedPools/__tests__/__snapshots__/index.spec.ts.snap @@ -100,7 +100,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "400000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -111,7 +111,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -138,7 +138,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "1000000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -149,7 +149,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -199,7 +199,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "14880000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -210,7 +210,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -237,7 +237,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "18600000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -248,7 +248,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -298,7 +298,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "14880000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -309,7 +309,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -336,7 +336,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "18600000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -347,7 +347,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -397,7 +397,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "56000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -408,7 +408,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -435,7 +435,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "80000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -446,7 +446,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -485,7 +485,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "14880000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -496,7 +496,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -523,7 +523,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "18600000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -534,7 +534,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -584,7 +584,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "borrowCapTokens": "14880000", "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -595,7 +595,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -622,7 +622,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "supplyCapTokens": "18600000", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -633,7 +633,7 @@ exports[`api/queries/getIsolatedPools > returns isolated pools in the correct fo "type": "rewardDistributor", }, { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", diff --git a/src/clients/api/queries/getIsolatedPools/formatOutput/formatDistributions.ts b/src/clients/api/queries/getIsolatedPools/formatOutput/formatDistributions.ts index 1db11a27d8..e1b0a1582b 100644 --- a/src/clients/api/queries/getIsolatedPools/formatOutput/formatDistributions.ts +++ b/src/clients/api/queries/getIsolatedPools/formatOutput/formatDistributions.ts @@ -3,7 +3,7 @@ import { logError } from 'errors'; import { AssetDistribution, Token } from 'types'; import findTokenByAddress from 'utilities/findTokenByAddress'; -import formatDistribution from 'utilities/formatDistribution'; +import formatRewardDistribution from 'utilities/formatRewardDistribution'; import multiplyMantissaDaily from 'utilities/multiplyMantissaDaily'; import { RewardsDistributorSettingsResult } from '../getRewardsDistributorSettingsMapping'; @@ -72,8 +72,7 @@ const formatDistributions = ({ }); supplyDistributions.push( - formatDistribution({ - type: 'rewardDistributor', + formatRewardDistribution({ rewardToken, rewardTokenPriceDollars, dailyDistributedRewardTokens: supplyDailyDistributedRewardTokens, @@ -94,8 +93,7 @@ const formatDistributions = ({ }); borrowDistributions.push( - formatDistribution({ - type: 'rewardDistributor', + formatRewardDistribution({ rewardToken, rewardTokenPriceDollars, dailyDistributedRewardTokens: borrowDailyDistributedRewardTokens, diff --git a/src/clients/api/queries/getMainPool/__tests__/__snapshots__/index.spec.ts.snap b/src/clients/api/queries/getMainPool/__tests__/__snapshots__/index.spec.ts.snap index 03d4823cc5..b2e94dd424 100644 --- a/src/clients/api/queries/getMainPool/__tests__/__snapshots__/index.spec.ts.snap +++ b/src/clients/api/queries/getMainPool/__tests__/__snapshots__/index.spec.ts.snap @@ -1,6 +1,701 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`api/queries/getMainPool > returns main pool in the correct format 1`] = ` +exports[`getMainPool > fetches and formats Prime distributions 1`] = ` +{ + "pool": { + "assets": [ + { + "borrowApyPercentage": "2.020093454326921", + "borrowBalanceCents": "1531484732509246.5984849465", + "borrowBalanceTokens": "15314985159958.905615", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "0.000004204786496764", + "dailyDistributedTokens": "499.9999968", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "3.2516057037486057", + "token": { + "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", + "asset": "/src/packages/tokens/img/usdc.svg", + "decimals": 6, + "symbol": "USDC", + }, + "type": "prime", + }, + { + "apyPercentage": "2.0200781032923", + "referenceValues": { + "userBorrowBalanceTokens": "66012867068.78838627155172413793", + "userSupplyBalanceTokens": "15951129109546755.24488088106947945753", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", + "asset": "/src/packages/tokens/img/usdc.svg", + "decimals": 6, + "symbol": "USDC", + }, + "type": "primeSimulation", + }, + ], + "borrowPercentageRatePerBlock": "1.902601834e-9", + "borrowerCount": 232, + "cashTokens": "10177700134473657708.145832", + "collateralFactor": 0.81, + "exchangeRateVTokens": "47.12928160687045990827", + "isCollateralOfUser": true, + "liquidityCents": "1.0177608535172447445226458687512e+21", + "reserveFactor": 0.1, + "reserveTokens": "895077567957714.356605", + "supplierCount": 638, + "supplyApyPercentage": "0.000002707894064535", + "supplyBalanceCents": "1.017672878050748282876538601630879295798481873e+21", + "supplyBalanceTokens": "10176820371890829846.23400212232789390903", + "supplyCapTokens": undefined, + "supplyDistributions": [ + { + "apyPercentage": "8.104628e-12", + "dailyDistributedTokens": "499.9999968", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "2.942340858505288", + "token": { + "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", + "asset": "/src/packages/tokens/img/usdc.svg", + "decimals": 6, + "symbol": "USDC", + }, + "type": "prime", + }, + { + "apyPercentage": "2.3265798060834175", + "referenceValues": { + "userBorrowBalanceTokens": "66012867068.78838627155172413793", + "userSupplyBalanceTokens": "15951129109546755.24488088106947945753", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", + "asset": "/src/packages/tokens/img/usdc.svg", + "decimals": 6, + "symbol": "USDC", + }, + "type": "primeSimulation", + }, + ], + "supplyPercentageRatePerBlock": "2.576e-15", + "tokenPriceCents": "99.9991", + "userBorrowBalanceCents": "1025.9446664149", + "userBorrowBalanceTokens": "10.259539", + "userPercentOfLimit": 0, + "userSupplyBalanceCents": "0", + "userSupplyBalanceTokens": "0", + "userWalletBalanceCents": "2132727770.8442098979", + "userWalletBalanceTokens": "21327469.655669", + "vToken": { + "address": "0xD5C4C2e2facBEB59D0216D0595d63FcDc6F9A1a7", + "decimals": 8, + "symbol": "vUSDC", + "underlyingToken": { + "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", + "asset": "/src/packages/tokens/img/usdc.svg", + "decimals": 6, + "symbol": "USDC", + }, + }, + }, + { + "borrowApyPercentage": "2.0200781032592374", + "borrowBalanceCents": "3485089196.568960612289", + "borrowBalanceTokens": "34852739.509411", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "1.8648777370665615", + "dailyDistributedTokens": "499.9999968", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "3.2516057037486057", + "token": { + "address": "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + "asset": "/src/packages/tokens/img/usdt.svg", + "decimals": 6, + "symbol": "USDT", + }, + "type": "prime", + }, + { + "apyPercentage": "2.0200781032923", + "referenceValues": { + "userBorrowBalanceTokens": "110643.61749019365079365079", + "userSupplyBalanceTokens": "1.358795738297682434014619091383216952499549585717162e+31", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + "asset": "/src/packages/tokens/img/usdt.svg", + "decimals": 6, + "symbol": "USDT", + }, + "type": "primeSimulation", + }, + ], + "borrowPercentageRatePerBlock": "1.902587519e-9", + "borrowerCount": 315, + "cashTokens": "1.0000736633870942714346486469034873074965e+34", + "collateralFactor": 0.8, + "exchangeRateVTokens": "49.8066352191466231613", + "isCollateralOfUser": false, + "liquidityCents": "1.000020649482198121567319896178714953634329610535e+36", + "reserveFactor": 0.1, + "reserveTokens": "90239.46739", + "supplierCount": 736, + "supplyApyPercentage": "0", + "supplyBalanceCents": "1.0000206494821981215674308946489344879323086222609070040463539e+36", + "supplyBalanceTokens": "1.00007366338709427143475965125804767703966849508783161e+34", + "supplyCapTokens": undefined, + "supplyDistributions": [ + { + "apyPercentage": "0", + "dailyDistributedTokens": "499.9999968", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "2.942340858505288", + "token": { + "address": "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + "asset": "/src/packages/tokens/img/usdt.svg", + "decimals": 6, + "symbol": "USDT", + }, + "type": "prime", + }, + { + "apyPercentage": "2.3265798060834175", + "referenceValues": { + "userBorrowBalanceTokens": "110643.61749019365079365079", + "userSupplyBalanceTokens": "1.358795738297682434014619091383216952499549585717162e+31", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + "asset": "/src/packages/tokens/img/usdt.svg", + "decimals": 6, + "symbol": "USDT", + }, + "type": "primeSimulation", + }, + ], + "supplyPercentageRatePerBlock": "0", + "tokenPriceCents": "99.994699", + "userBorrowBalanceCents": "2910453779.083287189529", + "userBorrowBalanceTokens": "29106080.704171", + "userPercentOfLimit": 20.88, + "userSupplyBalanceCents": "0", + "userSupplyBalanceTokens": "0", + "userWalletBalanceCents": "9.99936990530102198515010885786122152e+23", + "userWalletBalanceTokens": "9.999900000000021986315603448e+21", + "vToken": { + "address": "0xb7526572FFE56AB9D7489838Bf2E18e3323b441A", + "decimals": 8, + "symbol": "vUSDT", + "underlyingToken": { + "address": "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + "asset": "/src/packages/tokens/img/usdt.svg", + "decimals": 6, + "symbol": "USDT", + }, + }, + }, + { + "borrowApyPercentage": "2.0200781054088957", + "borrowBalanceCents": "24169165020757.008441931257037560073982", + "borrowBalanceTokens": "241756764974.648356207573518227", + "borrowCapTokens": "1e-18", + "borrowDistributions": [ + { + "apyPercentage": "0", + "dailyDistributedTokens": "0", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "3.2516057037486057", + "token": { + "address": "0x8301F2213c0eeD49a7E28Ae4c3e91722919B8B47", + "asset": "/src/packages/tokens/img/busd.svg", + "decimals": 18, + "symbol": "BUSD", + }, + "type": "prime", + }, + { + "apyPercentage": "2.0200781032923", + "referenceValues": { + "userBorrowBalanceTokens": "453577420.21510010545510979029", + "userSupplyBalanceTokens": "447427293535420150.537063455989777353", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0x8301F2213c0eeD49a7E28Ae4c3e91722919B8B47", + "asset": "/src/packages/tokens/img/busd.svg", + "decimals": 18, + "symbol": "BUSD", + }, + "type": "primeSimulation", + }, + ], + "borrowPercentageRatePerBlock": "1.902587521e-9", + "borrowerCount": 533, + "cashTokens": "1.00000000081005191374669097434774150829e+21", + "collateralFactor": 0.8, + "exchangeRateVTokens": "49.19405324154271215086", + "isCollateralOfUser": true, + "liquidityCents": "9.997306608098337343642424406007106875921571714e+22", + "reserveFactor": 1, + "reserveTokens": "144641814.793335107336610818", + "supplierCount": 2235, + "supplyApyPercentage": "0", + "supplyBalanceCents": "9.997306610513807812587592904169392833357345287278e+22", + "supplyBalanceTokens": "1.00000000105166403645033682413715238395883e+21", + "supplyCapTokens": "0", + "supplyDistributions": [ + { + "apyPercentage": "0", + "dailyDistributedTokens": "0", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + { + "apyPercentage": "2.942340858505288", + "token": { + "address": "0x8301F2213c0eeD49a7E28Ae4c3e91722919B8B47", + "asset": "/src/packages/tokens/img/busd.svg", + "decimals": 18, + "symbol": "BUSD", + }, + "type": "prime", + }, + { + "apyPercentage": "2.3265798060834175", + "referenceValues": { + "userBorrowBalanceTokens": "453577420.21510010545510979029", + "userSupplyBalanceTokens": "447427293535420150.537063455989777353", + "userXvsStakedTokens": "1000", + }, + "token": { + "address": "0x8301F2213c0eeD49a7E28Ae4c3e91722919B8B47", + "asset": "/src/packages/tokens/img/busd.svg", + "decimals": 18, + "symbol": "BUSD", + }, + "type": "primeSimulation", + }, + ], + "supplyPercentageRatePerBlock": "0", + "tokenPriceCents": "99.973066", + "userBorrowBalanceCents": "1338837412.548739840355458352155454", + "userBorrowBalanceTokens": "13391981.121682712425319219", + "userPercentOfLimit": 9.6, + "userSupplyBalanceCents": "9998773841.247562211290167508487802", + "userSupplyBalanceTokens": "100014676.365407881071589497", + "userWalletBalanceCents": "991176944992.458190249853510306844198", + "userWalletBalanceTokens": "9914439805.141698767644612503", + "vToken": { + "address": "0x08e0A5575De71037aE36AbfAfb516595fE68e5e4", + "decimals": 8, + "symbol": "vBUSD", + "underlyingToken": { + "address": "0x8301F2213c0eeD49a7E28Ae4c3e91722919B8B47", + "asset": "/src/packages/tokens/img/busd.svg", + "decimals": 18, + "symbol": "BUSD", + }, + }, + }, + { + "borrowApyPercentage": "115.47009997593621", + "borrowBalanceCents": "9021918820.590883118756440045847812", + "borrowBalanceTokens": "420107.243695434405914978", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "2.164334566357984", + "dailyDistributedTokens": "1499.9999904", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "borrowPercentageRatePerBlock": "7.3103099209e-8", + "borrowerCount": 261, + "cashTokens": "0.105662880026401893", + "collateralFactor": 0.8, + "exchangeRateVTokens": "12.34263792471376930792", + "isCollateralOfUser": true, + "liquidityCents": "2269.139464396223447322223722", + "reserveFactor": 0.25, + "reserveTokens": "34415.359297941913897345", + "supplierCount": 1700, + "supplyApyPercentage": "87.24219968627402", + "supplyBalanceCents": "8282841765.51712892855171530689666104", + "supplyBalanceTokens": "385691.99006037251840106076", + "supplyCapTokens": undefined, + "supplyDistributions": [ + { + "apyPercentage": "2.3597132480044802", + "dailyDistributedTokens": "1499.9999904", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "supplyPercentageRatePerBlock": "5.9719560501e-8", + "tokenPriceCents": "21475.275554", + "userBorrowBalanceCents": "1507200562.34337075516290466079759", + "userBorrowBalanceTokens": "70183.060447978210615835", + "userPercentOfLimit": 10.81, + "userSupplyBalanceCents": "7153834234.921780428502293762497558", + "userSupplyBalanceTokens": "333119.554947424281534427", + "userWalletBalanceCents": "804.0192840488722", + "userWalletBalanceTokens": "0.0374393", + "vToken": { + "address": "0x2E7222e51c0f6e98610A1543Aa3836E092CDe62c", + "decimals": 8, + "symbol": "vBNB", + "underlyingToken": { + "address": "0x0000000000000000000000000000000000000000", + "asset": "/src/packages/tokens/img/bnb.svg", + "decimals": 18, + "isNative": true, + "symbol": "BNB", + }, + }, + }, + { + "borrowApyPercentage": "14.678237213106993", + "borrowBalanceCents": "127494789.976725601230307532916064", + "borrowBalanceTokens": "361325.374254367115070152", + "borrowCapTokens": "270000", + "borrowDistributions": [ + { + "apyPercentage": "0", + "dailyDistributedTokens": "0", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "borrowPercentageRatePerBlock": "1.3031372027e-8", + "borrowerCount": 77, + "cashTokens": "2300786.088596467449092226", + "collateralFactor": 0.6, + "exchangeRateVTokens": "48.11796090116306607589", + "isCollateralOfUser": true, + "liquidityCents": "811839577.423293023548242500951832", + "reserveFactor": 0.25, + "reserveTokens": "24426.24486633871943155", + "supplierCount": 326, + "supplyApyPercentage": "1.4173055905260634", + "supplyBalanceCents": "930715490.39593208582245151364324", + "supplyBalanceTokens": "2637685.21798449584350707", + "supplyCapTokens": undefined, + "supplyDistributions": [ + { + "apyPercentage": "0", + "dailyDistributedTokens": "0", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "supplyPercentageRatePerBlock": "1.338834523e-9", + "tokenPriceCents": "352.853132", + "userBorrowBalanceCents": "0", + "userBorrowBalanceTokens": "0", + "userPercentOfLimit": 0, + "userSupplyBalanceCents": "358170693.46469177600488881036144", + "userSupplyBalanceTokens": "1015070.18354790110239092", + "userWalletBalanceCents": "1974853275.785316380184811695133004", + "userWalletBalanceTokens": "5596813.792162446726375697", + "vToken": { + "address": "0x6d6F697e34145Bb95c54E77482d97cc261Dc237E", + "decimals": 8, + "symbol": "vXVS", + "underlyingToken": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + }, + }, + { + "borrowApyPercentage": "12.929398759456245", + "borrowBalanceCents": "2189606786.6192461112257626", + "borrowBalanceTokens": "13355.332641776432517388", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "1.4813202523229263", + "dailyDistributedTokens": "250.0000128", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "borrowPercentageRatePerBlock": "1.1568959578e-8", + "borrowerCount": 51, + "cashTokens": "0.862760988737629408", + "collateralFactor": 0.8, + "exchangeRateVTokens": "41.89012550143102255692", + "isCollateralOfUser": false, + "liquidityCents": "141449.6641035343414416", + "reserveFactor": 0.1, + "reserveTokens": "212.852150854368890484", + "supplierCount": 211, + "supplyApyPercentage": "11.761843113429581", + "supplyBalanceCents": "2154851126.1507758655837335175", + "supplyBalanceTokens": "13143.34325191080125394165", + "supplyCapTokens": undefined, + "supplyDistributions": [ + { + "apyPercentage": "1.5053909671967736", + "dailyDistributedTokens": "250.0000128", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "supplyPercentageRatePerBlock": "1.0580000116e-8", + "tokenPriceCents": "163950", + "userBorrowBalanceCents": "4183840.15655389583477625", + "userBorrowBalanceTokens": "25.519000649917022475", + "userPercentOfLimit": 0.03, + "userSupplyBalanceCents": "0", + "userSupplyBalanceTokens": "0", + "userWalletBalanceCents": "14100032453.0672534166492135", + "userWalletBalanceTokens": "86002.02777107199400213", + "vToken": { + "address": "0x162D005F0Fff510E54958Cfc5CF32A3180A84aab", + "decimals": 8, + "symbol": "vETH", + "underlyingToken": { + "address": "0x98f7A83361F7Ac8765CcEBAB1425da6b341958a7", + "asset": "/src/packages/tokens/img/eth.svg", + "decimals": 18, + "symbol": "ETH", + }, + }, + }, + { + "borrowApyPercentage": "4.99318031366e-7", + "borrowBalanceCents": "1.00013156763973925145e-14", + "borrowBalanceTokens": "100.013156763973925145", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "1000000", + "dailyDistributedTokens": "25.0000000000000128", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "borrowPercentageRatePerBlock": "4.75e-16", + "borrowerCount": 2, + "cashTokens": "1000014902.000227060658744583", + "collateralFactor": 0, + "exchangeRateVTokens": "49.8409052160961750676", + "isCollateralOfUser": false, + "liquidityCents": "1.000014902000227060658744583e-7", + "reserveFactor": 0.2, + "reserveTokens": "0.002676765141333725", + "supplierCount": 5, + "supplyApyPercentage": "0", + "supplyBalanceCents": "1.00001500201070705929733581893e-7", + "supplyBalanceTokens": "1000015002.01070705929733581893", + "supplyCapTokens": "0", + "supplyDistributions": [ + { + "apyPercentage": "1000000", + "dailyDistributedTokens": "25.0000000000000128", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "supplyPercentageRatePerBlock": "0", + "tokenPriceCents": "1e-16", + "userBorrowBalanceCents": "0", + "userBorrowBalanceTokens": "0", + "userPercentOfLimit": 0, + "userSupplyBalanceCents": "0", + "userSupplyBalanceTokens": "0", + "userWalletBalanceCents": "0", + "userWalletBalanceTokens": "0", + "vToken": { + "address": "0xF206af85BC2761c4F876d27Bd474681CfB335EfA", + "decimals": 8, + "symbol": "vUST", + "underlyingToken": { + "address": "0x5A79efD958432E72211ee73D5DDFa9bc8f248b5F", + "asset": "/src/packages/tokens/img/ust.svg", + "decimals": 18, + "symbol": "UST", + }, + }, + }, + { + "borrowApyPercentage": "2.0200781032592374", + "borrowBalanceCents": "0", + "borrowBalanceTokens": "0", + "borrowCapTokens": undefined, + "borrowDistributions": [ + { + "apyPercentage": "1000000", + "dailyDistributedTokens": "37.5000000000000192", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "borrowPercentageRatePerBlock": "1.902587519e-9", + "borrowerCount": 0, + "cashTokens": "10103376", + "collateralFactor": 0, + "exchangeRateVTokens": "0.5", + "isCollateralOfUser": false, + "liquidityCents": "1.0103376e-9", + "reserveFactor": 0.25, + "reserveTokens": "0", + "supplierCount": 3, + "supplyApyPercentage": "0", + "supplyBalanceCents": "1.0103376e-9", + "supplyBalanceTokens": "10103376", + "supplyCapTokens": "0", + "supplyDistributions": [ + { + "apyPercentage": "1000000", + "dailyDistributedTokens": "37.5000000000000192", + "token": { + "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", + "asset": "/src/packages/tokens/img/xvs.svg", + "decimals": 18, + "symbol": "XVS", + }, + "type": "rewardDistributor", + }, + ], + "supplyPercentageRatePerBlock": "0", + "tokenPriceCents": "1e-16", + "userBorrowBalanceCents": "0", + "userBorrowBalanceTokens": "0", + "userPercentOfLimit": 0, + "userSupplyBalanceCents": "0", + "userSupplyBalanceTokens": "0", + "userWalletBalanceCents": "0", + "userWalletBalanceTokens": "0", + "vToken": { + "address": "0x9C3015191d39cF1930F92EB7e7BCbd020bCA286a", + "decimals": 8, + "symbol": "vLUNA", + "underlyingToken": { + "address": "0xf36160EC62E3B191EA375dadfe465E8Fa1F8CabB", + "asset": "/src/packages/tokens/img/luna.svg", + "decimals": 6, + "symbol": "LUNA", + }, + }, + }, + ], + "comptrollerAddress": "0xa258a693A403b7e98fd05EE9e1558C760308cFC7", + "description": "Fake pool description", + "isIsolated": false, + "name": "Fake pool name", + "userBorrowBalanceCents": "5760676620.076618095782139262953044", + "userBorrowLimitCents": "13936988877.014289177436902303005152", + "userSupplyBalanceCents": "17510778769.6340344157973500813468", + }, +} +`; + +exports[`getMainPool > returns main pool in the correct format 1`] = ` { "pool": { "assets": [ @@ -408,7 +1103,7 @@ exports[`api/queries/getMainPool > returns main pool in the correct format 1`] = "borrowCapTokens": undefined, "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -435,7 +1130,7 @@ exports[`api/queries/getMainPool > returns main pool in the correct format 1`] = "supplyCapTokens": "0", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "25.0000000000000128", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -474,7 +1169,7 @@ exports[`api/queries/getMainPool > returns main pool in the correct format 1`] = "borrowCapTokens": undefined, "borrowDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "37.5000000000000192", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", @@ -501,7 +1196,7 @@ exports[`api/queries/getMainPool > returns main pool in the correct format 1`] = "supplyCapTokens": "0", "supplyDistributions": [ { - "apyPercentage": "Infinity", + "apyPercentage": "1000000", "dailyDistributedTokens": "37.5000000000000192", "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", diff --git a/src/clients/api/queries/getMainPool/__tests__/index.spec.ts b/src/clients/api/queries/getMainPool/__tests__/index.spec.ts index feea9b97b6..3a98d5d6f0 100644 --- a/src/clients/api/queries/getMainPool/__tests__/index.spec.ts +++ b/src/clients/api/queries/getMainPool/__tests__/index.spec.ts @@ -1,7 +1,14 @@ -import { MainPoolComptroller, ResilientOracle, VaiController, VenusLens } from 'packages/contracts'; +import { + MainPoolComptroller, + Prime, + ResilientOracle, + VaiController, + VenusLens, +} from 'packages/contracts'; import Vi from 'vitest'; import fakeMainPoolComptrollerContractResponses from '__mocks__/contracts/mainPoolComptroller'; +import fakePrimeContractResponses from '__mocks__/contracts/prime'; import fakeVenusLensContractResponses from '__mocks__/contracts/venusLens'; import fakeAccountAddress, { altAddress } from '__mocks__/models/address'; import { markets } from '__mocks__/models/markets'; @@ -58,7 +65,7 @@ const fakeVenusLensContract = { }, } as unknown as VenusLens; -describe('api/queries/getMainPool', () => { +describe('getMainPool', () => { beforeEach(() => { (getMainMarkets as Vi.Mock).mockImplementation(() => ({ markets })); }); @@ -81,4 +88,31 @@ describe('api/queries/getMainPool', () => { expect(fakeVaiControllerContract.callStatic.accrueVAIInterest).toHaveBeenCalledTimes(1); expect(response).toMatchSnapshot(); }); + + it('fetches and formats Prime distributions', async () => { + const fakePrimeContract = { + MINIMUM_STAKED_XVS: async () => fakePrimeContractResponses.MINIMUM_STAKED_XVS, + getAllMarkets: async () => fakePrimeContractResponses.getAllMarkets, + estimateAPR: async () => fakePrimeContractResponses.estimateAPR, + calculateAPR: async () => fakePrimeContractResponses.calculateAPR, + } as unknown as Prime; + + const response = await getMainPool({ + name: 'Fake pool name', + description: 'Fake pool description', + xvs, + vai, + tokens, + accountAddress: fakeAccountAddress, + mainPoolComptrollerContract: fakeMainPoolComptrollerContract, + venusLensContract: fakeVenusLensContract, + vaiControllerContract: fakeVaiControllerContract, + resilientOracleContract: fakeResilientOracleContract, + primeContract: fakePrimeContract, + }); + + // Check VAI interests were accrued before being fetched + expect(fakeVaiControllerContract.callStatic.accrueVAIInterest).toHaveBeenCalledTimes(1); + expect(response).toMatchSnapshot(); + }); }); diff --git a/src/clients/api/queries/getMainPool/appendPrimeSimulationDistributions.ts b/src/clients/api/queries/getMainPool/appendPrimeSimulationDistributions.ts new file mode 100644 index 0000000000..a6a53414f9 --- /dev/null +++ b/src/clients/api/queries/getMainPool/appendPrimeSimulationDistributions.ts @@ -0,0 +1,98 @@ +import BigNumber from 'bignumber.js'; +import { Prime } from 'packages/contracts'; +import { Asset, Token } from 'types'; +import { + areAddressesEqual, + convertAprToApy, + convertTokensToWei, + convertWeiToTokens, +} from 'utilities'; + +import { NULL_ADDRESS } from 'constants/address'; + +export interface ResolvePrimeSimulationDistributionsInput { + primeContract: Prime; + primeVTokenAddresses: string[]; + assets: Asset[]; + xvs: Token; + primeMinimumXvsToStakeMantissa: BigNumber; + accountAddress?: string; +} + +export const appendPrimeSimulationDistributions = async ({ + primeContract, + primeVTokenAddresses, + assets, + xvs, + accountAddress, + primeMinimumXvsToStakeMantissa, +}: ResolvePrimeSimulationDistributionsInput) => { + const primeMinimumXvsToStakeTokens = convertWeiToTokens({ + valueWei: primeMinimumXvsToStakeMantissa, + token: xvs, + }); + + return Promise.allSettled( + primeVTokenAddresses.map(primeVTokenAddress => { + const asset = assets.find(poolAsset => + areAddressesEqual(poolAsset.vToken.address, primeVTokenAddress), + ); + + if (!asset) { + return undefined; + } + + const promise = async () => { + const averageBorrowBalanceTokens = asset.borrowBalanceTokens.dividedBy(asset.borrowerCount); + const averageBorrowBalanceMantissa = convertTokensToWei({ + value: averageBorrowBalanceTokens, + token: asset.vToken.underlyingToken, + }); + + const averageSupplyBalanceTokens = asset.supplyBalanceTokens.dividedBy(asset.supplierCount); + const averageSupplyBalanceMantissa = convertTokensToWei({ + value: averageSupplyBalanceTokens, + token: asset.vToken.underlyingToken, + }); + + const simulatedPrimeAprs = await primeContract.estimateAPR( + primeVTokenAddress, + accountAddress || NULL_ADDRESS, + averageBorrowBalanceMantissa.toFixed(), + averageSupplyBalanceMantissa.toFixed(), + primeMinimumXvsToStakeMantissa.toFixed(), + ); + + const referenceValues = { + userSupplyBalanceTokens: averageSupplyBalanceTokens, + userBorrowBalanceTokens: averageBorrowBalanceTokens, + userXvsStakedTokens: primeMinimumXvsToStakeTokens, + }; + + const borrowSimulatedPrimeApy = convertAprToApy({ + aprBips: simulatedPrimeAprs.borrowAPR.toString(), + }); + + asset.borrowDistributions.push({ + type: 'primeSimulation', + token: asset.vToken.underlyingToken, + apyPercentage: borrowSimulatedPrimeApy, + referenceValues, + }); + + const supplySimulatedPrimeApy = convertAprToApy({ + aprBips: simulatedPrimeAprs.supplyAPR.toString(), + }); + + asset.supplyDistributions.push({ + type: 'primeSimulation', + token: asset.vToken.underlyingToken, + apyPercentage: supplySimulatedPrimeApy, + referenceValues, + }); + }; + + return promise(); + }), + ); +}; diff --git a/src/clients/api/queries/getMainPool/formatToPool/formatDistributions.ts b/src/clients/api/queries/getMainPool/formatToPool/formatDistributions.ts new file mode 100644 index 0000000000..b2d4d31a41 --- /dev/null +++ b/src/clients/api/queries/getMainPool/formatToPool/formatDistributions.ts @@ -0,0 +1,47 @@ +import BigNumber from 'bignumber.js'; +import { AssetDistribution, Token, VToken } from 'types'; +import { formatRewardDistribution, multiplyMantissaDaily } from 'utilities'; + +export interface FormatDistributionsInput { + xvsSpeedMantissa: BigNumber; + balanceDollars: BigNumber; + xvsPriceDollars: BigNumber; + xvs: Token; + vToken: VToken; + primeApy?: BigNumber; +} + +export const formatDistributions = ({ + xvsSpeedMantissa, + balanceDollars, + xvsPriceDollars, + xvs, + vToken, + primeApy, +}: FormatDistributionsInput) => { + const dailyDistributedXvs = multiplyMantissaDaily({ + mantissa: xvsSpeedMantissa, + decimals: xvs.decimals, + }); + + const xvsDistribution = formatRewardDistribution({ + rewardToken: xvs, + rewardTokenPriceDollars: xvsPriceDollars, + dailyDistributedRewardTokens: dailyDistributedXvs, + balanceDollars, + }); + + const distributions: AssetDistribution[] = [xvsDistribution]; + + if (primeApy) { + const primeDistribution: AssetDistribution = { + type: 'prime', + apyPercentage: primeApy, + token: vToken.underlyingToken, + }; + + distributions.push(primeDistribution); + } + + return distributions; +}; diff --git a/src/clients/api/queries/getMainPool/formatToPool.ts b/src/clients/api/queries/getMainPool/formatToPool/index.ts similarity index 92% rename from src/clients/api/queries/getMainPool/formatToPool.ts rename to src/clients/api/queries/getMainPool/formatToPool/index.ts index ca900445fc..8448d54343 100644 --- a/src/clients/api/queries/getMainPool/formatToPool.ts +++ b/src/clients/api/queries/getMainPool/formatToPool/index.ts @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js'; import { logError } from 'errors'; import { MainPoolComptroller, ResilientOracle, VenusLens } from 'packages/contracts'; -import { Asset, AssetDistribution, Market, Pool, Token, VToken } from 'types'; +import { Asset, Market, Pool, Token, VToken } from 'types'; import { addUserPropsToPool, areAddressesEqual, @@ -10,7 +10,6 @@ import { convertFactorFromSmartContract, convertPriceMantissaToDollars, convertWeiToTokens, - formatDistribution, multiplyMantissaDaily, } from 'utilities'; @@ -19,6 +18,9 @@ import { COMPOUND_DECIMALS, COMPOUND_MANTISSA } from 'constants/compoundMantissa import MAX_UINT256 from 'constants/maxUint256'; import findTokenByAddress from 'utilities/findTokenByAddress'; +import { PrimeApy } from '../types'; +import { formatDistributions } from './formatDistributions'; + const BSC_MAINNET_VCAN_MAIN_POOL_ADDRESS = '0xeBD0070237a0713E8D94fEf1B728d3d993d290ef'; export interface FormatToPoolInput { @@ -41,13 +43,14 @@ export interface FormatToPoolInput { Awaited> >[]; xvsPriceMantissa: BigNumber; + primeApyMap: Map; userCollateralizedVTokenAddresses?: string[]; userVTokenBalances?: Awaited>; userVaiBorrowBalanceWei?: BigNumber; mainMarkets?: Market[]; } -const formatToPool = ({ +export const formatToPool = ({ name, xvs, vai, @@ -64,6 +67,7 @@ const formatToPool = ({ userCollateralizedVTokenAddresses, userVTokenBalances, userVaiBorrowBalanceWei, + primeApyMap, mainMarkets, }: FormatToPoolInput) => { const assets: Asset[] = []; @@ -155,11 +159,6 @@ const formatToPool = ({ const userVTokenBalancesResult = userVTokenBalances?.[index]; - const xvsPriceDollars = convertPriceMantissaToDollars({ - priceMantissa: xvsPriceMantissa, - token: xvs, - }); - const tokenPriceDollars = convertPriceMantissaToDollars({ priceMantissa: underlyingTokenPriceMantissa, token: underlyingToken, @@ -251,36 +250,29 @@ const formatToPool = ({ const borrowBalanceDollars = borrowBalanceTokens.multipliedBy(tokenPriceDollars); const borrowBalanceCents = convertDollarsToCents(borrowBalanceDollars); - const borrowDailyDistributedXvs = multiplyMantissaDaily({ - mantissa: xvsBorrowSpeedMantissa, - decimals: xvs.decimals, + const xvsPriceDollars = convertPriceMantissaToDollars({ + priceMantissa: xvsPriceMantissa, + token: xvs, }); - const borrowXvsDistribution = formatDistribution({ - type: 'rewardDistributor', - rewardToken: xvs, - rewardTokenPriceDollars: xvsPriceDollars, - dailyDistributedRewardTokens: borrowDailyDistributedXvs, + const borrowDistributions = formatDistributions({ + xvsSpeedMantissa: xvsBorrowSpeedMantissa, balanceDollars: borrowBalanceDollars, + xvsPriceDollars, + xvs, + vToken, + primeApy: primeApyMap.get(vToken.address)?.borrowApy, }); - const borrowDistributions: AssetDistribution[] = [borrowXvsDistribution]; - - const supplyDailyDistributedXvs = multiplyMantissaDaily({ - mantissa: xvsSupplySpeedMantissa, - decimals: xvs.decimals, - }); - - const supplyXvsDistribution = formatDistribution({ - type: 'rewardDistributor', - rewardToken: xvs, - rewardTokenPriceDollars: xvsPriceDollars, - dailyDistributedRewardTokens: supplyDailyDistributedXvs, + const supplyDistributions = formatDistributions({ + xvsSpeedMantissa: xvsSupplySpeedMantissa, balanceDollars: supplyBalanceDollars, + xvsPriceDollars, + xvs, + vToken, + primeApy: primeApyMap.get(vToken.address)?.supplyApy, }); - const supplyDistributions: AssetDistribution[] = [supplyXvsDistribution]; - const isCollateralOfUser = (userCollateralizedVTokenAddresses || []).includes( vTokenMetaData.vToken, ); diff --git a/src/clients/api/queries/getMainPool/index.ts b/src/clients/api/queries/getMainPool/index.ts index 14bfec3fea..85974d7c6a 100644 --- a/src/clients/api/queries/getMainPool/index.ts +++ b/src/clients/api/queries/getMainPool/index.ts @@ -1,25 +1,13 @@ import BigNumber from 'bignumber.js'; -import { logError } from 'errors'; - -import extractSettledPromiseValue from 'utilities/extractSettledPromiseValue'; +import { convertAprToApy, extractSettledPromiseValue } from 'utilities'; import getMainMarkets from '../getMainMarkets'; -import formatToPool from './formatToPool'; -import { GetMainPoolInput, GetMainPoolOutput } from './types'; +import { appendPrimeSimulationDistributions } from './appendPrimeSimulationDistributions'; +import { formatToPool } from './formatToPool'; +import { GetMainPoolInput, GetMainPoolOutput, PrimeApy } from './types'; export type { GetMainPoolInput, GetMainPoolOutput } from './types'; -// Since the borrower and supplier counts aren't essential information, we make the logic so the -// dApp can still function if the API is down -const safelyGetMainMarkets = async () => { - try { - const res = await getMainMarkets(); - return res; - } catch (error) { - logError(error); - } -}; - const getMainPool = async ({ name, description, @@ -31,11 +19,14 @@ const getMainPool = async ({ venusLensContract, vaiControllerContract, resilientOracleContract, + primeContract, }: GetMainPoolInput): Promise => { const [ marketsResult, mainMarkets, xvsPriceMantissaResult, + primeVTokenAddressesResult, + primeMinimumXvsToStakeResult, assetsInResult, // eslint-disable-next-line @typescript-eslint/naming-convention _accrueVaiInterestResult, @@ -45,9 +36,12 @@ const getMainPool = async ({ mainPoolComptrollerContract.getAllMarkets(), // Fetch main markets to get the supplier and borrower counts // TODO: fetch borrower and supplier counts from subgraph once available - safelyGetMainMarkets(), + getMainMarkets(), // Fetch XVS price resilientOracleContract.getPrice(xvs.address), + // Prime related calls + primeContract?.getAllMarkets(), + primeContract?.MINIMUM_STAKED_XVS(), // Account related calls accountAddress ? mainPoolComptrollerContract.getAssetsIn(accountAddress) : undefined, // Call (statically) accrueVAIInterest to calculate past accrued interests before fetching all @@ -66,6 +60,8 @@ const getMainPool = async ({ } const vTokenAddresses = marketsResult.value; + const primeVTokenAddresses = extractSettledPromiseValue(primeVTokenAddressesResult) || []; + const primeMinimumXvsToStakeMantissa = extractSettledPromiseValue(primeMinimumXvsToStakeResult); // Fetch underlying token prices const underlyingTokenPricePromises = Promise.allSettled( @@ -102,17 +98,45 @@ const getMainPool = async ({ : undefined, ]); + // Fetch Prime distributions + const primeAprPromises = primeContract + ? Promise.allSettled( + accountAddress + ? primeVTokenAddresses.map(primeVTokenAddress => + primeContract.calculateAPR(primeVTokenAddress, accountAddress), + ) + : [], + ) + : undefined; + const underlyingTokenPriceResults = await underlyingTokenPricePromises; const borrowCapsResults = await borrowCapsPromises; const supplyCapsResults = await supplyCapsPromises; const xvsBorrowSpeedResults = await xvsBorrowSpeedPromises; const xvsSupplySpeedResults = await xvsSupplySpeedPromises; const [vTokenMetaDataResults, userVTokenBalancesResults] = await vTokenMetaDataPromises; + const primeAprResults = (await primeAprPromises) || []; if (vTokenMetaDataResults.status === 'rejected') { throw new Error(vTokenMetaDataResults.reason); } + const primeApyMap = new Map(); + primeAprResults.forEach((primeAprResult, index) => { + if (primeAprResult.status !== 'fulfilled') { + return; + } + + const primeApr = primeAprResult.value; + + const apys: PrimeApy = { + borrowApy: convertAprToApy({ aprBips: primeApr?.borrowAPR.toString() || '0' }), + supplyApy: convertAprToApy({ aprBips: primeApr?.supplyAPR.toString() || '0' }), + }; + + primeApyMap.set(primeVTokenAddresses[index], apys); + }); + const vaiRepayAmountMantissa = extractSettledPromiseValue(vaiRepayAmountResult); const pool = formatToPool({ @@ -134,9 +158,22 @@ const getMainPool = async ({ userVaiBorrowBalanceWei: vaiRepayAmountMantissa ? new BigNumber(vaiRepayAmountMantissa.toString()) : undefined, + primeApyMap, mainMarkets: extractSettledPromiseValue(mainMarkets)?.markets, }); + // Fetch Prime simulations and add them to distributions + if (primeContract && primeMinimumXvsToStakeMantissa) { + await appendPrimeSimulationDistributions({ + assets: pool.assets, + primeContract, + primeVTokenAddresses, + accountAddress, + primeMinimumXvsToStakeMantissa: new BigNumber(primeMinimumXvsToStakeMantissa.toString()), + xvs, + }); + } + return { pool, }; diff --git a/src/clients/api/queries/getMainPool/types.ts b/src/clients/api/queries/getMainPool/types.ts index 014560bfeb..38648708c7 100644 --- a/src/clients/api/queries/getMainPool/types.ts +++ b/src/clients/api/queries/getMainPool/types.ts @@ -1,4 +1,11 @@ -import { MainPoolComptroller, ResilientOracle, VaiController, VenusLens } from 'packages/contracts'; +import BigNumber from 'bignumber.js'; +import { + MainPoolComptroller, + Prime, + ResilientOracle, + VaiController, + VenusLens, +} from 'packages/contracts'; import { Pool, Token } from 'types'; export interface GetMainPoolInput { @@ -11,9 +18,15 @@ export interface GetMainPoolInput { venusLensContract: VenusLens; resilientOracleContract: ResilientOracle; vaiControllerContract: VaiController; + primeContract?: Prime; accountAddress?: string; } export interface GetMainPoolOutput { pool: Pool; } + +export interface PrimeApy { + borrowApy: BigNumber; + supplyApy: BigNumber; +} diff --git a/src/clients/api/queries/getMainPool/useGetMainPool.ts b/src/clients/api/queries/getMainPool/useGetMainPool.ts index b6e5a9e900..974b0c4184 100644 --- a/src/clients/api/queries/getMainPool/useGetMainPool.ts +++ b/src/clients/api/queries/getMainPool/useGetMainPool.ts @@ -1,5 +1,6 @@ import { useGetMainPoolComptrollerContract, + useGetPrimeContract, useGetResilientOracleContract, useGetVaiControllerContract, useGetVenusLensContract, @@ -11,6 +12,7 @@ import { callOrThrow, generatePseudoRandomRefetchInterval } from 'utilities'; import getMainPool, { GetMainPoolInput, GetMainPoolOutput } from 'clients/api/queries/getMainPool'; import FunctionKey from 'constants/functionKey'; +import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; type TrimmedInput = Omit< GetMainPoolInput, @@ -42,11 +44,15 @@ const useGetMainPool = (input: TrimmedInput, options?: Options) => { const xvs = useGetToken({ symbol: 'XVS' }); const vai = useGetToken({ symbol: 'VAI' }); const tokens = useGetTokens(); + const isPrimeEnabled = useIsFeatureEnabled({ + name: 'prime', + }); const mainPoolComptrollerContract = useGetMainPoolComptrollerContract(); const venusLensContract = useGetVenusLensContract(); const resilientOracleContract = useGetResilientOracleContract(); const vaiControllerContract = useGetVaiControllerContract(); + const primeContract = useGetPrimeContract(); return useQuery( [FunctionKey.GET_MAIN_POOL, input], @@ -65,6 +71,7 @@ const useGetMainPool = (input: TrimmedInput, options?: Options) => { name: t('mainPool.name'), description: t('mainPool.description'), tokens, + primeContract: isPrimeEnabled ? primeContract : undefined, ...input, ...params, }), diff --git a/src/clients/api/queries/getVaiRepayApy/__snapshots__/index.spec.ts.snap b/src/clients/api/queries/getVaiRepayApy/__snapshots__/index.spec.ts.snap index 324de54924..0531b596f2 100644 --- a/src/clients/api/queries/getVaiRepayApy/__snapshots__/index.spec.ts.snap +++ b/src/clients/api/queries/getVaiRepayApy/__snapshots__/index.spec.ts.snap @@ -2,6 +2,6 @@ exports[`api/queries/getVaiRepayApy > returns the VAI repay APY in the correct format on success 1`] = ` { - "repayApyPercentage": "Infinity", + "repayApyPercentage": "1000000", } `; diff --git a/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap b/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap index 35ee2887eb..bda88760c1 100644 --- a/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap +++ b/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap @@ -170,6 +170,11 @@ exports[`api/queries/useGetPool > returns the correct asset 1`] = ` }, { "apyPercentage": "1.015310564979612374", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -206,6 +211,11 @@ exports[`api/queries/useGetPool > returns the correct asset 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -290,6 +300,11 @@ exports[`api/queries/useGetPool > returns the correct asset 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", diff --git a/src/clients/api/queries/useGetPools/__snapshots__/index.spec.tsx.snap b/src/clients/api/queries/useGetPools/__snapshots__/index.spec.tsx.snap index 76f299bf1a..b124e6437c 100644 --- a/src/clients/api/queries/useGetPools/__snapshots__/index.spec.tsx.snap +++ b/src/clients/api/queries/useGetPools/__snapshots__/index.spec.tsx.snap @@ -171,6 +171,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.015310564979612374", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -207,6 +212,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -291,6 +301,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -498,6 +513,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.015310564979612374", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -534,6 +554,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", @@ -618,6 +643,11 @@ exports[`api/queries/useGetPools > returns data in the correct format 1`] = ` }, { "apyPercentage": "1.753105649796123742", + "referenceValues": { + "userBorrowBalanceTokens": "1000", + "userSupplyBalanceTokens": "1000", + "userXvsStakedTokens": "1000", + }, "token": { "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", "asset": "/src/packages/tokens/img/xvs.svg", diff --git a/src/components/AccountData/__snapshots__/index.spec.tsx.snap b/src/components/AccountData/__snapshots__/index.spec.tsx.snap index ace72f1a96..fbd4ff37a4 100644 --- a/src/components/AccountData/__snapshots__/index.spec.tsx.snap +++ b/src/components/AccountData/__snapshots__/index.spec.tsx.snap @@ -1,35 +1,33 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`components/AccountData > renders correctly for core pool asset: { action: 'borrow', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'borrow', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'borrow', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0100.00Borrow limit used6.4%13.03%Daily earnings$0.02$0.05"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'borrow', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0100.00Borrow limit used6.4%13.03%Daily earnings$0.03$0.05"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'repay', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'repay', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'repay', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'repay', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$123.33Max:$1.92K.Borrow balance (XVS)0Borrow limit used6.4%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'supply', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.92K.Supply balance (XVS)90.00Borrow limit$1.92KDaily earnings$0.02"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'supply', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.92K.Supply balance (XVS)90.00Borrow limit$1.92KDaily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'supply', amountToken: 100000 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$65.86K.Supply balance (XVS)90.00100.09KBorrow limit$1.92K$65.86KDaily earnings$0.02$0.62"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'supply', amountToken: 100000 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$65.86K.Supply balance (XVS)90.00100.09KBorrow limit$1.92K$65.86KDaily earnings$0.03$0.62"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'supply', amountToken: 100000, isAssetIsolated: true } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$65.86K.Supply balance (XVS)90.00100.09KBorrow limit$1.92K$65.86KDaily earnings$0.02$0.62"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'withdraw', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.92K.Supply balance (XVS)90.00Borrow limit$1.92KDaily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'withdraw', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.92K.Supply balance (XVS)90.00Borrow limit$1.92KDaily earnings$0.02"`; +exports[`components/AccountData > renders correctly for core pool asset: { action: 'withdraw', amountToken: 50 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.89K.Supply balance (XVS)90.0040.00Borrow limit$1.92K$1.89KDaily earnings$0.03"`; -exports[`components/AccountData > renders correctly for core pool asset: { action: 'withdraw', amountToken: 50 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$123.33Max:$1.89K.Supply balance (XVS)90.0040.00Borrow limit$1.92K$1.89KDaily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'borrow', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'borrow', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'borrow', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0100.00Borrow limit used0%Daily earnings$0.03$0.05"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'borrow', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0100.00Borrow limit used0%Daily earnings$0.02$0.05"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'repay', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'repay', amountToken: +0 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'repay', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'repay', amountToken: 100 } 1`] = `"Borrow APY-2.3%Distribution APY (XVS)4.17%Total APY-6.48%Current:$0Max:$0Borrow balance (XVS)0Borrow limit used0%Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'supply', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$0Supply balance (XVS)90.00Borrow limit$0Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'supply', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$0Supply balance (XVS)90.00Borrow limit$0Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'supply', amountToken: 100000 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$63.93KSupply balance (XVS)90.00100.09KBorrow limit$0$63.93KDaily earnings$0.03$0.62"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'supply', amountToken: 100000 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$63.93KSupply balance (XVS)90.00100.09KBorrow limit$0$63.93KDaily earnings$0.02$0.62"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'withdraw', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$0Supply balance (XVS)90.00Borrow limit$0Daily earnings$0.03"`; -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'withdraw', amountToken: +0 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:$0Supply balance (XVS)90.00Borrow limit$0Daily earnings$0.02"`; - -exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'withdraw', amountToken: 50 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:-$31.96Supply balance (XVS)90.0040.00Borrow limit$0-$31.96Daily earnings$0.02"`; +exports[`components/AccountData > renders correctly for isolated pool asset: { action: 'withdraw', amountToken: 50 } 1`] = `"Supply APY0.05%Distribution APY (XVS)0.11%Total APY0.16%Current:$0Max:-$31.96Supply balance (XVS)90.0040.00Borrow limit$0-$31.96Daily earnings$0.03"`; diff --git a/src/components/AccountData/index.spec.tsx b/src/components/AccountData/index.spec.tsx index 625ff4739e..2610f4df7c 100644 --- a/src/components/AccountData/index.spec.tsx +++ b/src/components/AccountData/index.spec.tsx @@ -21,14 +21,13 @@ describe('components/AccountData', () => { it.each([ { action: 'supply', amountToken: 0 }, { action: 'supply', amountToken: 100000 }, - { action: 'supply', amountToken: 100000, isAssetIsolated: true }, { action: 'withdraw', amountToken: 0 }, { action: 'withdraw', amountToken: 50 }, { action: 'borrow', amountToken: 0 }, { action: 'borrow', amountToken: 100 }, { action: 'repay', amountToken: 100 }, { action: 'repay', amountToken: 0 }, - ] as { action: AccountDataProps['action']; amountToken: number; isAssetIsolated?: boolean }[])( + ] as { action: AccountDataProps['action']; amountToken: number }[])( 'renders correctly for core pool asset: %s', async ({ action, amountToken }) => { const { container } = renderComponent( diff --git a/src/components/AccountData/useAssetInfo.ts b/src/components/AccountData/useAssetInfo.ts index 5aa1b48f22..6e19d394c8 100644 --- a/src/components/AccountData/useAssetInfo.ts +++ b/src/components/AccountData/useAssetInfo.ts @@ -31,11 +31,16 @@ const useAssetInfo = ({ asset, type }: UseAssetInfoInput) => { const distributionRows = ( type === 'borrow' ? asset.borrowDistributions : asset.supplyDistributions - ).map(distribution => ({ - label: t('assetInfo.distributionApy', { tokenSymbol: distribution.token.symbol }), - iconSrc: distribution.token, - children: formatPercentageToReadableValue(distribution.apyPercentage), - })); + ) + .filter(distribution => distribution.type !== 'primeSimulation') + .map(distribution => ({ + label: + distribution.type === 'prime' + ? t('assetInfo.primeApy', { tokenSymbol: distribution.token.symbol }) + : t('assetInfo.distributionApy', { tokenSymbol: distribution.token.symbol }), + iconSrc: distribution.token, + children: formatPercentageToReadableValue(distribution.apyPercentage), + })); const combinedDistributionApys = getCombinedDistributionApys({ asset, diff --git a/src/components/Table/styles.ts b/src/components/Table/styles.ts index 34c310c4a9..949020908b 100644 --- a/src/components/Table/styles.ts +++ b/src/components/Table/styles.ts @@ -114,7 +114,7 @@ export const useStyles = () => { `, table: ({ minWidth }: { minWidth: string }) => css` min-width: ${minWidth}; - table-layout: fixed; + table-layout: auto; .MuiTableCell-root { border-width: 0; diff --git a/src/containers/MarketTable/Apy/ApyWithPrimeBoost/index.tsx b/src/containers/MarketTable/Apy/ApyWithPrimeBoost/index.tsx index 45513b871f..d5643552a2 100644 --- a/src/containers/MarketTable/Apy/ApyWithPrimeBoost/index.tsx +++ b/src/containers/MarketTable/Apy/ApyWithPrimeBoost/index.tsx @@ -6,41 +6,43 @@ import useFormatPercentageToReadableValue from 'hooks/useFormatPercentageToReada export interface ApyWithPrimeBoostProps { type: 'supply' | 'borrow'; - supplyApyPercentage: BigNumber; - borrowApyPercentage: BigNumber; - distributionsSupplyApyRewardsPercentage: BigNumber; - distributionsBorrowApyRewardsPercentage: BigNumber; - readableApy: string; + apyPercentage: BigNumber; + apyPercentageWithoutPrimeBoost: BigNumber; readableLtv: string; } export const ApyWithPrimeBoost: React.FC = ({ type, - supplyApyPercentage, - borrowApyPercentage, - distributionsSupplyApyRewardsPercentage, - distributionsBorrowApyRewardsPercentage, - readableApy, + apyPercentage, + apyPercentageWithoutPrimeBoost, readableLtv, }) => { const { t } = useTranslation(); + const readableApy = useFormatPercentageToReadableValue({ + value: apyPercentage, + }); + const readableApyWithoutPrime = useFormatPercentageToReadableValue({ - value: - type === 'supply' - ? supplyApyPercentage.plus(distributionsSupplyApyRewardsPercentage) - : borrowApyPercentage.minus(distributionsBorrowApyRewardsPercentage), + value: apyPercentageWithoutPrimeBoost, }); return ( -
-
-

- {readableApyWithoutPrime}{' '} - {readableApy} +

+
+

+ + {readableApyWithoutPrime} + {' '} + + {readableApy} +

- +
diff --git a/src/containers/MarketTable/Apy/ApyWithPrimeSimulationBoost/index.tsx b/src/containers/MarketTable/Apy/ApyWithPrimeSimulationBoost/index.tsx index 13c6069a55..cfed892f4d 100644 --- a/src/containers/MarketTable/Apy/ApyWithPrimeSimulationBoost/index.tsx +++ b/src/containers/MarketTable/Apy/ApyWithPrimeSimulationBoost/index.tsx @@ -1,48 +1,74 @@ -import BigNumber from 'bignumber.js'; import { Icon, Link, Tooltip } from 'components'; import { useTranslation } from 'translation'; +import { PrimeSimulationDistribution, Token } from 'types'; import { PRIME_DOC_URL } from 'constants/prime'; import useFormatPercentageToReadableValue from 'hooks/useFormatPercentageToReadableValue'; +import useFormatTokensToReadableValue from 'hooks/useFormatTokensToReadableValue'; export interface ApyWithPrimeSimulationBoostProps { type: 'supply' | 'borrow'; - apyPrimeSimulationBoost: BigNumber; + primeSimulationDistribution: PrimeSimulationDistribution; readableApy: string; readableLtv: string; + xvs: Token; } export const ApyWithPrimeSimulationBoost: React.FC = ({ type, - apyPrimeSimulationBoost, + primeSimulationDistribution, readableApy, readableLtv, + xvs, }) => { const { t, Trans } = useTranslation(); - const readableApyPrimeSimulationBoost = useFormatPercentageToReadableValue({ - value: apyPrimeSimulationBoost, + const readablePrimeApy = useFormatPercentageToReadableValue({ + value: primeSimulationDistribution.apyPercentage, + }); + + const readableReferenceXvsStaked = useFormatTokensToReadableValue({ + value: primeSimulationDistribution.referenceValues.userXvsStakedTokens, + token: xvs, + addSymbol: true, + }); + + const readableReferenceSupplyBalance = useFormatTokensToReadableValue({ + value: primeSimulationDistribution.referenceValues.userSupplyBalanceTokens, + token: primeSimulationDistribution.token, + addSymbol: true, + }); + + const readableReferenceBorrowBalance = useFormatTokensToReadableValue({ + value: primeSimulationDistribution.referenceValues.userBorrowBalanceTokens, + token: primeSimulationDistribution.token, + addSymbol: true, }); return (

{readableApy}{' '} - {type === 'supply' && <>/ {readableLtv}} + {type === 'supply' && <>/ {readableLtv}}

-
-

+

+

{t('marketTable.apy.primeSimulationBoost.label', { - apyPrimeBoost: `${type === 'supply' ? '+' : '-'}${readableApyPrimeSimulationBoost}`, + apyPrimeBoost: `${type === 'supply' ? '+' : '-'}${readablePrimeApy}`, })}

e.stopPropagation()} />, }} diff --git a/src/containers/MarketTable/Apy/index.tsx b/src/containers/MarketTable/Apy/index.tsx index 9b887d56c9..28b848ce0f 100644 --- a/src/containers/MarketTable/Apy/index.tsx +++ b/src/containers/MarketTable/Apy/index.tsx @@ -1,6 +1,7 @@ import { LayeredValues } from 'components'; +import { useGetToken } from 'packages/tokens'; import { useMemo } from 'react'; -import { Asset } from 'types'; +import { Asset, PrimeDistribution, PrimeSimulationDistribution } from 'types'; import { getCombinedDistributionApys } from 'utilities'; import useFormatPercentageToReadableValue from 'hooks/useFormatPercentageToReadableValue'; @@ -17,61 +18,74 @@ export interface ApyProps { export const Apy: React.FC = ({ asset, column }) => { const type = column === 'supplyApyLtv' || column === 'labeledSupplyApyLtv' ? 'supply' : 'borrow'; + const xvs = useGetToken({ + symbol: 'XVS', + }); const combinedDistributionApys = useMemo(() => getCombinedDistributionApys({ asset }), [asset]); + const { primeDistribution, primeSimulationDistribution } = useMemo(() => { + const result: { + primeSimulationDistribution?: PrimeSimulationDistribution; + primeDistribution?: PrimeDistribution; + } = {}; + + const distributions = type === 'borrow' ? asset.borrowDistributions : asset.supplyDistributions; + + distributions.forEach(distribution => { + if (distribution.type === 'prime') { + result.primeDistribution = distribution; + } else if (distribution.type === 'primeSimulation') { + result.primeSimulationDistribution = distribution; + } + }); + + return result; + }, [asset.borrowDistributions, asset.supplyDistributions, type]); + + const apyPercentage = + type === 'borrow' + ? asset.borrowApyPercentage.minus(combinedDistributionApys.totalBorrowApyPercentage) + : asset.supplyApyPercentage.plus(combinedDistributionApys.totalSupplyApyPercentage); + const readableApy = useFormatPercentageToReadableValue({ - value: - type === 'supply' - ? asset.supplyApyPercentage.plus(combinedDistributionApys.totalSupplyApyPercentage) - : asset.borrowApyPercentage.minus(combinedDistributionApys.totalBorrowApyPercentage), + value: apyPercentage, }); const readableLtv = useFormatPercentageToReadableValue({ value: +asset.collateralFactor * 100, }); - const apyPrimeBoost = - type === 'supply' - ? combinedDistributionApys.supplyApyPrimePercentage - : combinedDistributionApys.borrowApyPrimePercentage; - // Display Prime boost - if (apyPrimeBoost) { + if (primeDistribution && primeDistribution.apyPercentage.isGreaterThan(0)) { + const apyPercentageWithoutPrimeBoost = + type === 'borrow' + ? apyPercentage.plus(primeDistribution.apyPercentage) + : apyPercentage.minus(primeDistribution.apyPercentage); + return ( ); } - const apyPrimeSimulationBoost = - type === 'supply' - ? combinedDistributionApys.supplyApyPrimeSimulationPercentage - : combinedDistributionApys.borrowApyPrimeSimulationPercentage; - // Display hypothetical Prime boost - if (apyPrimeSimulationBoost) { + if (primeSimulationDistribution && primeSimulationDistribution.apyPercentage.isGreaterThan(0)) { return ( ); } - // No Prime boost or hypothetical Prime boost to display + // No Prime boost or Prime boost simulation to display // Display supply APY if (type === 'supply') { diff --git a/src/pages/Account/AccountBreakdown/PoolsBreakdown/__snapshots__/index.spec.tsx.snap b/src/pages/Account/AccountBreakdown/PoolsBreakdown/__snapshots__/index.spec.tsx.snap index 5d1000a9f1..9335ec2729 100644 --- a/src/pages/Account/AccountBreakdown/PoolsBreakdown/__snapshots__/index.spec.tsx.snap +++ b/src/pages/Account/AccountBreakdown/PoolsBreakdown/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`PoolsBreakdown > displays content correctly 1`] = `"Supplied assetsAssetAPY / LTVBalancesorted descendingCollateralXVS0.16%50%90.00 XVS$115.08USDC5.24% 5.99%80%100.00 USDC$99.99Sort bySupply balance​XVSAPY / LTV0.16%50%Balance90.00 XVS$115.08CollateralUSDCAPY / LTV5.24% 5.99%80%Balance100.00 USDC$99.99CollateralBorrowed assetsAssetAPY Balancesorted descending% of limitBUSD-4.9% -3.98%50.00 BUSD$50.000%USDT-5.49% -1.01% Prime boost40.00 USDT$40.000%Sort byBorrow balance​BUSDAPY -4.9% -3.98%Balance50.00 BUSD$50.00% of limit0%USDTAPY -5.49% -1.01% Prime boostBalance40.00 USDT$40.00% of limit0%AssetsSuppliedBorrowAssetAPY / LTVBalancesorted descendingCollateralXVS0.16%50%90.00 XVS$115.08USDC5.24% 5.99%80%100.00 USDC$99.99Sort bySupply balance​XVSAPY / LTV0.16%50%Balance90.00 XVS$115.08CollateralUSDCAPY / LTV5.24% 5.99%80%Balance100.00 USDC$99.99Collateral"`; +exports[`PoolsBreakdown > displays content correctly 1`] = `"Supplied assetsAssetAPY / LTVBalancesorted descendingCollateralXVS0.16%50%90.00 XVS$115.08USDC5.24% 5.99%80%100.00 USDC$99.99Sort bySupply balance​XVSAPY / LTV0.16%50%Balance90.00 XVS$115.08CollateralUSDCAPY / LTV5.24% 5.99%80%Balance100.00 USDC$99.99CollateralBorrowed assetsAssetAPY Balancesorted descending% of limitBUSD-4.9% -5.81%50.00 BUSD$50.000%USDT-5.49% -1.01% Prime boost40.00 USDT$40.000%Sort byBorrow balance​BUSDAPY -4.9% -5.81%Balance50.00 BUSD$50.00% of limit0%USDTAPY -5.49% -1.01% Prime boostBalance40.00 USDT$40.00% of limit0%AssetsSuppliedBorrowAssetAPY / LTVBalancesorted descendingCollateralXVS0.16%50%90.00 XVS$115.08USDC5.24% 5.99%80%100.00 USDC$99.99Sort bySupply balance​XVSAPY / LTV0.16%50%Balance90.00 XVS$115.08CollateralUSDCAPY / LTV5.24% 5.99%80%Balance100.00 USDC$99.99Collateral"`; diff --git a/src/pages/Account/AccountBreakdown/Summary/__snapshots__/index.spec.tsx.snap b/src/pages/Account/AccountBreakdown/Summary/__snapshots__/index.spec.tsx.snap index 5d3b3a1e88..c79141deb4 100644 --- a/src/pages/Account/AccountBreakdown/Summary/__snapshots__/index.spec.tsx.snap +++ b/src/pages/Account/AccountBreakdown/Summary/__snapshots__/index.spec.tsx.snap @@ -2,6 +2,6 @@ exports[`pages/Account/Summary > displays account health when passing displayAccountHealth prop as true 1`] = `"Borrow limit used:6.4%Limit:$1.92K.Your safe limit:$1.54K"`; -exports[`pages/Account/Summary > displays stats correctly 1`] = `"Net APY0%Daily Earnings$0.05Total Supply$1.23MTotal Borrow$123.33"`; +exports[`pages/Account/Summary > displays stats correctly 1`] = `"Net APY0%Daily Earnings$0.06Total Supply$1.23MTotal Borrow$123.33"`; -exports[`pages/Account/Summary > displays total vault stake when passing vaults prop and displayTotalVaultStake prop as true 1`] = `"Net APY0%Daily Earnings$0.13Total Supply$1.23MTotal Borrow$123.33Total Vault Stake$233.00"`; +exports[`pages/Account/Summary > displays total vault stake when passing vaults prop and displayTotalVaultStake prop as true 1`] = `"Net APY0%Daily Earnings$0.14Total Supply$1.23MTotal Borrow$123.33Total Vault Stake$233.00"`; diff --git a/src/pages/Dashboard/__snapshots__/index.spec.tsx.snap b/src/pages/Dashboard/__snapshots__/index.spec.tsx.snap index e3fbbf74f2..5e753a83c2 100644 --- a/src/pages/Dashboard/__snapshots__/index.spec.tsx.snap +++ b/src/pages/Dashboard/__snapshots__/index.spec.tsx.snap @@ -1,5 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`pages/Dashboard > displays markets table correctly 1`] = `"AssetPoolWalletsorted descendingSupply APY / LTVBorrow APY LiquidityUSDTVenus900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MUSDTMetaverse900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MXVSVenus100.00 XVS$127.860.16%50%-6.48%10.00 XVS$80.36MXVSMetaverse100.00 XVS$127.860.16%50%-6.48%10.00 XVS$80.36MBUSDVenus110.00 BUSD$110.003.56% / 80%+1.75% Prime boost-4.9% -3.98%10.00 BUSD$36.54MBUSDMetaverse110.00 BUSD$110.003.56% / 80%+1.75% Prime boost-4.9% -3.98%10.00 BUSD$36.54MUSDCVenus0 USDC$05.24% 5.99%80%-7.03% -6.11%10.00 USDC$17.02MUSDCMetaverse0 USDC$05.24% 5.99%80%-7.03% -6.11%10.00 USDC$17.02MSort byWallet balance​USDTPoolVenusWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34MUSDTPoolMetaverseWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34MXVSPoolVenusWallet100.00 XVS$127.86Supply APY / LTV0.16%50%Borrow APY -6.48%Liquidity10.00 XVS$80.36MXVSPoolMetaverseWallet100.00 XVS$127.86Supply APY / LTV0.16%50%Borrow APY -6.48%Liquidity10.00 XVS$80.36MBUSDPoolVenusWallet110.00 BUSD$110.00Supply APY / LTV3.56% / 80%+1.75% Prime boostBorrow APY -4.9% -3.98%Liquidity10.00 BUSD$36.54MBUSDPoolMetaverseWallet110.00 BUSD$110.00Supply APY / LTV3.56% / 80%+1.75% Prime boostBorrow APY -4.9% -3.98%Liquidity10.00 BUSD$36.54MUSDCPoolVenusWallet0 USDC$0Supply APY / LTV5.24% 5.99%80%Borrow APY -7.03% -6.11%Liquidity10.00 USDC$17.02MUSDCPoolMetaverseWallet0 USDC$0Supply APY / LTV5.24% 5.99%80%Borrow APY -7.03% -6.11%Liquidity10.00 USDC$17.02M"`; +exports[`pages/Dashboard > displays markets table correctly 1`] = `"AssetPoolWalletsorted descendingSupply APY / LTVBorrow APY LiquidityUSDTVenus900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MUSDTMetaverse900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MXVSVenus100.00 XVS$127.860.16%50%-6.48%10.00 XVS$80.36MXVSMetaverse100.00 XVS$127.860.16%50%-6.48%10.00 XVS$80.36MBUSDVenus110.00 BUSD$110.003.56% / 80%+1.75% Prime boost-4.9% -5.81%10.00 BUSD$36.54MBUSDMetaverse110.00 BUSD$110.003.56% / 80%+1.75% Prime boost-4.9% -5.81%10.00 BUSD$36.54MUSDCVenus0 USDC$05.24% 5.99%80%-7.03% -7.94%10.00 USDC$17.02MUSDCMetaverse0 USDC$05.24% 5.99%80%-7.03% -7.94%10.00 USDC$17.02MSort byWallet balance​USDTPoolVenusWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34MUSDTPoolMetaverseWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34MXVSPoolVenusWallet100.00 XVS$127.86Supply APY / LTV0.16%50%Borrow APY -6.48%Liquidity10.00 XVS$80.36MXVSPoolMetaverseWallet100.00 XVS$127.86Supply APY / LTV0.16%50%Borrow APY -6.48%Liquidity10.00 XVS$80.36MBUSDPoolVenusWallet110.00 BUSD$110.00Supply APY / LTV3.56% / 80%+1.75% Prime boostBorrow APY -4.9% -5.81%Liquidity10.00 BUSD$36.54MBUSDPoolMetaverseWallet110.00 BUSD$110.00Supply APY / LTV3.56% / 80%+1.75% Prime boostBorrow APY -4.9% -5.81%Liquidity10.00 BUSD$36.54MUSDCPoolVenusWallet0 USDC$0Supply APY / LTV5.24% 5.99%80%Borrow APY -7.03% -7.94%Liquidity10.00 USDC$17.02MUSDCPoolMetaverseWallet0 USDC$0Supply APY / LTV5.24% 5.99%80%Borrow APY -7.03% -7.94%Liquidity10.00 USDC$17.02M"`; exports[`pages/Dashboard > filters out assets when entering value in search input 1`] = `"AssetPoolWalletsorted descendingSupply APY / LTVBorrow APY LiquidityUSDTVenus900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MUSDTMetaverse900.00 USDT$900.004.01% / 80%+1.75% Prime boost-5.49% -1.01% Prime boost10.00 USDT$55.34MSort byWallet balance​USDTPoolVenusWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34MUSDTPoolMetaverseWallet900.00 USDT$900.00Supply APY / LTV4.01% / 80%+1.75% Prime boostBorrow APY -5.49% -1.01% Prime boostLiquidity10.00 USDT$55.34M"`; diff --git a/src/translation/translations/en.json b/src/translation/translations/en.json index deb2a07f3a..a3e5cbf0c4 100644 --- a/src/translation/translations/en.json +++ b/src/translation/translations/en.json @@ -96,6 +96,7 @@ "assetInfo": { "borrowApy": "Borrow APY", "distributionApy": "Distribution APY ({{tokenSymbol}})", + "primeApy": "Prime APY ({{tokenSymbol}})", "supplyApy": "Supply APY", "totalApy": { "borrowApyTooltip": "Borrow APY - Distribution APYs", @@ -393,7 +394,7 @@ }, "primeSimulationBoost": { "label": "{{apyPrimeBoost}} Prime boost", - "tooltip": "Learn more about Prime here" + "tooltip": "Prime boost based on an average user supply of {{supplyBalance}}, borrow of {{borrowBalance}} and vault stake of {{xvsStaked}}. Learn more about Prime" } }, "columnKeys": { diff --git a/src/types/index.ts b/src/types/index.ts index 9ca5724635..524657ca6b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -49,6 +49,11 @@ export interface PrimeSimulationDistribution { type: 'primeSimulation'; token: Token; apyPercentage: BigNumber; + referenceValues: { + userSupplyBalanceTokens: BigNumber; + userBorrowBalanceTokens: BigNumber; + userXvsStakedTokens: BigNumber; + }; } export type AssetDistribution = diff --git a/src/utilities/calculateApy/__tests__/index.spec.ts b/src/utilities/calculateApy/__tests__/index.spec.ts index abc6bbeff8..907f560dde 100644 --- a/src/utilities/calculateApy/__tests__/index.spec.ts +++ b/src/utilities/calculateApy/__tests__/index.spec.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; -import calculateApy from '..'; +import calculateApy, { MAX_VALUE, MIN_VALUE } from '..'; describe('calculateApy', () => { it('should calculate APY for given daily distributed tokens and decimals', () => { @@ -8,4 +8,12 @@ describe('calculateApy', () => { expect(calculateApy({ dailyRate })).toMatchInlineSnapshot('"0.4389579824776835"'); }); + + it('should bound returned value to maximum', () => { + expect(calculateApy({ dailyRate: Infinity })).toEqual(new BigNumber(MAX_VALUE)); + }); + + it('should bound returned value to minimum', () => { + expect(calculateApy({ dailyRate: -Infinity })).toEqual(new BigNumber(MIN_VALUE)); + }); }); diff --git a/src/utilities/calculateApy/index.ts b/src/utilities/calculateApy/index.ts index 1e41791cc0..62668e7aec 100644 --- a/src/utilities/calculateApy/index.ts +++ b/src/utilities/calculateApy/index.ts @@ -2,6 +2,10 @@ import BigNumber from 'bignumber.js'; import { COMPOUND_DECIMALS } from 'constants/compoundMantissa'; import { DAYS_PER_YEAR } from 'constants/daysPerYear'; +import { ONE_MILLION } from 'constants/numbers'; + +export const MIN_VALUE = -ONE_MILLION; +export const MAX_VALUE = ONE_MILLION; export interface CalculateApyInput { dailyRate: BigNumber | number | string; @@ -20,6 +24,12 @@ const calculateApy = ({ dailyRate }: CalculateApyInput) => { apy -= 1; apy *= 100; + if (apy > MAX_VALUE) { + apy = MAX_VALUE; + } else if (apy < MIN_VALUE) { + apy = MIN_VALUE; + } + return new BigNumber(apy).dp(COMPOUND_DECIMALS); }; diff --git a/src/utilities/calculateYearlyEarnings.spec.ts b/src/utilities/calculateYearlyEarnings.spec.ts index 0aa736297f..76caa8203f 100644 --- a/src/utilities/calculateYearlyEarnings.spec.ts +++ b/src/utilities/calculateYearlyEarnings.spec.ts @@ -19,6 +19,6 @@ describe('utilities/calculateYearlyEarnings', () => { assets, }); - expect(earnings?.toFixed()).toMatchInlineSnapshot('"1036"'); + expect(earnings?.toFixed()).toMatchInlineSnapshot('"1127"'); }); }); diff --git a/src/utilities/convertAprToApy/__tests__/index.spec.ts b/src/utilities/convertAprToApy/__tests__/index.spec.ts new file mode 100644 index 0000000000..cd2ed9976a --- /dev/null +++ b/src/utilities/convertAprToApy/__tests__/index.spec.ts @@ -0,0 +1,11 @@ +import { convertAprToApy } from '..'; + +describe('convertAprToApy', () => { + it('converts APR bips to APY', () => { + const res = convertAprToApy({ + aprBips: '23', + }); + + expect(res).toMatchInlineSnapshot('"2.3265798060834175"'); + }); +}); diff --git a/src/utilities/convertAprToApy/index.ts b/src/utilities/convertAprToApy/index.ts new file mode 100644 index 0000000000..9693797e4c --- /dev/null +++ b/src/utilities/convertAprToApy/index.ts @@ -0,0 +1,12 @@ +import BigNumber from 'bignumber.js'; + +import { DAYS_PER_YEAR } from 'constants/daysPerYear'; + +import calculateApy from '../calculateApy'; + +export const convertAprToApy = ({ aprBips }: { aprBips: string }) => { + // Convert bips to daily rate + const dailyRate = new BigNumber(aprBips).div(1000).div(DAYS_PER_YEAR); + // Convert daily rate to APY + return calculateApy({ dailyRate }); +}; diff --git a/src/utilities/formatDistribution.ts b/src/utilities/formatRewardDistribution.ts similarity index 75% rename from src/utilities/formatDistribution.ts rename to src/utilities/formatRewardDistribution.ts index 4c2c69e9d1..e9cec64233 100644 --- a/src/utilities/formatDistribution.ts +++ b/src/utilities/formatRewardDistribution.ts @@ -3,15 +3,13 @@ import { AssetDistribution, Token } from 'types'; import { calculateApy } from 'utilities'; export interface FormatDistributionInput { - type: AssetDistribution['type']; rewardToken: Token; rewardTokenPriceDollars: BigNumber; dailyDistributedRewardTokens: BigNumber; balanceDollars: BigNumber; } -const formatDistribution = ({ - type, +const formatRewardDistribution = ({ rewardToken, rewardTokenPriceDollars, dailyDistributedRewardTokens, @@ -29,20 +27,12 @@ const formatDistribution = ({ ), }); - if (type === 'rewardDistributor') { - return { - type, - token: rewardToken, - apyPercentage, - dailyDistributedTokens: dailyDistributedRewardTokens, - }; - } - return { - type, + type: 'rewardDistributor', token: rewardToken, apyPercentage, + dailyDistributedTokens: dailyDistributedRewardTokens, }; }; -export default formatDistribution; +export default formatRewardDistribution; diff --git a/src/utilities/getCombinedDistributionApys/__tests__/__snapshots__/index.spec.ts.snap b/src/utilities/getCombinedDistributionApys/__tests__/__snapshots__/index.spec.ts.snap index 41a032c957..778dc6588d 100644 --- a/src/utilities/getCombinedDistributionApys/__tests__/__snapshots__/index.spec.ts.snap +++ b/src/utilities/getCombinedDistributionApys/__tests__/__snapshots__/index.spec.ts.snap @@ -21,7 +21,7 @@ exports[`utilities/getCombinedDistributionApys > calculates combined distributio "supplyApyPrimePercentage": "0.753105649796123742", "supplyApyPrimeSimulationPercentage": undefined, "supplyApyRewardsPercentage": "1.353105649796123742", - "totalBorrowApyPercentage": "0.757221957894448989", + "totalBorrowApyPercentage": "2.583433257486696473", "totalSupplyApyPercentage": "2.106211299592247484", } `; @@ -47,7 +47,7 @@ exports[`utilities/getCombinedDistributionApys > calculates combined distributio "supplyApyPrimePercentage": undefined, "supplyApyPrimeSimulationPercentage": "1.753105649796123742", "supplyApyRewardsPercentage": "0.678420831753642169", - "totalBorrowApyPercentage": "-0.060408047620153028", + "totalBorrowApyPercentage": "1.765803251972094456", "totalSupplyApyPercentage": "0.678420831753642169", } `; diff --git a/src/utilities/getCombinedDistributionApys/index.ts b/src/utilities/getCombinedDistributionApys/index.ts index ce6a577dfb..e9e92b9ea5 100644 --- a/src/utilities/getCombinedDistributionApys/index.ts +++ b/src/utilities/getCombinedDistributionApys/index.ts @@ -60,7 +60,7 @@ const getCombinedDistributionApys = ({ asset }: GetCombinedDistributionApysInput supplyApyPrimeSimulationPercentage: supply.apyPrimeSimulationPercentage, borrowApyPrimeSimulationPercentage: borrow.apyPrimeSimulationPercentage, totalSupplyApyPercentage: supply.apyRewardsPercentage.plus(supply.apyPrimePercentage || 0), - totalBorrowApyPercentage: borrow.apyRewardsPercentage.minus(borrow.apyPrimePercentage || 0), + totalBorrowApyPercentage: borrow.apyRewardsPercentage.plus(borrow.apyPrimePercentage || 0), }; }; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index bd74b7d866..cca2674409 100755 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -39,8 +39,10 @@ export { default as multiplyMantissaDaily } from './multiplyMantissaDaily'; export { default as callOrThrow } from './callOrThrow'; export { default as convertPriceMantissaToDollars } from './convertPriceMantissaToDollars'; export { default as convertFactorFromSmartContract } from './convertFactorFromSmartContract'; -export { default as formatDistribution } from './formatDistribution'; +export { default as formatRewardDistribution } from './formatRewardDistribution'; export { default as findTokenByAddress } from './findTokenByAddress'; export * from './cn'; export * from './createStoreSelectors'; export * from './notifications'; +export * from './convertAprToApy'; +export { default as extractSettledPromiseValue } from './extractSettledPromiseValue';