diff --git a/src/subgraph/index.ts b/src/subgraph/index.ts index 64d7d4d..7b0ec1a 100644 --- a/src/subgraph/index.ts +++ b/src/subgraph/index.ts @@ -22,6 +22,7 @@ import { getTotalPoolsValue, getUserTotalPoolsValue, getUserVaultDataByChain, + getVaultApr, getVaultDataByChain, } from './vaults'; import { Pyth } from '../pyth'; @@ -222,4 +223,9 @@ export class Subgraph { const subgraphEndpoint = this.getSubgraphEndpoint(this.rpcConfig.chainId); return await getUserTotalPoolsValue(userAddress, this.rpcConfig.chainId, subgraphEndpoint, this.pyth.pythClient); } + + public async getVaultApr(vaultId: string): Promise<{ apr7Days: Decimal; apr30Days: Decimal; aprAllTime: Decimal }> { + const subgraphEndpoint = this.getSubgraphEndpoint(this.rpcConfig.chainId); + return await getVaultApr(subgraphEndpoint, vaultId); + } } diff --git a/src/subgraph/vaults/index.ts b/src/subgraph/vaults/index.ts index 317d68e..e766da8 100644 --- a/src/subgraph/vaults/index.ts +++ b/src/subgraph/vaults/index.ts @@ -1,19 +1,15 @@ import { request } from 'graphql-request'; import { Vault, VaultPositionsResponse } from '../../interfaces'; -import { fetchAllVaultsQuery, fetchUserAllVaultsQuery } from './subgraphQueries'; +import { fetchAllVaultsQuery, fetchUserAllVaultsQuery, fetchVaultAprDetails } from './subgraphQueries'; import { mapVaultsArrayToInterface } from '../../common/subgraphMapper'; import { NotFoundError } from '../../error/not-found.error'; import { Chain as SupportedChain, availableVaultsPerChain } from '@parifi/references'; -import { PRICE_FEED_DECIMALS, getNormalizedPriceByIdFromPriceIdArray } from '../../common'; +import { DECIMAL_ZERO, PRICE_FEED_DECIMALS, getNormalizedPriceByIdFromPriceIdArray } from '../../common'; import Decimal from 'decimal.js'; import { getLatestPricesFromPyth, normalizePythPriceForParifi } from '../../pyth/pyth'; import { AxiosInstance } from 'axios'; -// const matchChain: Record = { -// [SupportedChain.ARBITRUM_SEPOLIA]: arbitrumSepolia, -// }; - // Get all vaults from subgraph export const getAllVaults = async (subgraphEndpoint: string): Promise => { try { @@ -135,3 +131,55 @@ export const getTotalPoolsValue = async ( const totalPoolValue = data.reduce((a, b) => a + b.totatVaultValue, 0); return { data, totalPoolValue }; }; + +export const getVaultApr = async ( + subgraphEndpoint: string, + vaultId: string, +): Promise<{ apr7Days: Decimal; apr30Days: Decimal; aprAllTime: Decimal }> => { + // Interface for subgraph response + interface VaultAprInterface { + vaultDailyDatas: { + apr: Decimal; + vault: { + allTimeApr: Decimal; + }; + }[]; + } + + let apr7Days: Decimal = DECIMAL_ZERO; + let apr30Days: Decimal = DECIMAL_ZERO; + let aprAllTime: Decimal = DECIMAL_ZERO; + + try { + const subgraphResponse: VaultAprInterface = await request(subgraphEndpoint, fetchVaultAprDetails(vaultId)); + console.log(subgraphResponse); + const vaultDatas = subgraphResponse.vaultDailyDatas; + + // If no APR data found, return 0; + if (vaultDatas.length == 0) { + return { apr7Days, apr30Days, aprAllTime }; + } else { + // Set All Time APR for the vault from response + aprAllTime = vaultDatas[0].vault.allTimeApr; + } + + /// Calculate the APR of vault based on timeframe data. If enough data points are not available, + /// the value is set to 0; + for (let index = 0; index < vaultDatas.length; index++) { + const vaultData = vaultDatas[index]; + + // Do not calculate 7 day APR if less than 7 days of data is available + if (index < 7 && vaultDatas.length >= 7) { + apr7Days = apr7Days.add(vaultData.apr); + } + + // Do not calculate 30 day APR if less than 30 days of data is available + if (index < 30 && vaultDatas.length >= 30) { + apr30Days = apr30Days.add(vaultData.apr); + } + } + return { apr7Days: apr7Days.div(7), apr30Days: apr30Days.div(30), aprAllTime: aprAllTime }; + } catch (error) { + throw error; + } +}; diff --git a/src/subgraph/vaults/subgraphQueries.ts b/src/subgraph/vaults/subgraphQueries.ts index 8ebe669..2dcda4b 100644 --- a/src/subgraph/vaults/subgraphQueries.ts +++ b/src/subgraph/vaults/subgraphQueries.ts @@ -76,3 +76,17 @@ query vaultInfo { } } `; + +export const fetchVaultAprDetails = (vaultId: string) => gql` + { + vaultDailyDatas( + first: 30 + orderBy: startTimestamp + orderDirection: desc + where: { vault: "${vaultId}" } + ) { + vault { allTimeApr } + apr + } + } +`; diff --git a/test/subgraph-tests/vaults.test.ts b/test/subgraph-tests/vaults.test.ts index a90055b..39f2df0 100644 --- a/test/subgraph-tests/vaults.test.ts +++ b/test/subgraph-tests/vaults.test.ts @@ -1,14 +1,33 @@ import { Chain } from '@parifi/references'; import { PRICE_FEED_DECIMALS, ParifiSdk } from '../../src'; -import { RpcConfig } from '../../src/interfaces/classConfigs'; import { assert } from 'ethers'; import Decimal from 'decimal.js'; +import { PythConfig, RelayerConfig, RelayerI, RpcConfig, SubgraphConfig } from '../../src/interfaces/classConfigs'; const rpcConfig: RpcConfig = { - chainId: Chain.ARBITRUM_SEPOLIA, + chainId: Chain.ARBITRUM_MAINNET, }; -const parifiSdk = new ParifiSdk(rpcConfig, {}, {}, {}); +const subgraphConfig: SubgraphConfig = { + subgraphEndpoint: process.env.SUBGRAPH_ENDPOINT, +}; + +const pythConfig: PythConfig = { + pythEndpoint: process.env.PYTH_SERVICE_ENDPOINT, + username: process.env.PYTH_SERVICE_USERNAME, + password: process.env.PYTH_SERVICE_PASSWORD, + isStable: true, +}; + +const gelatoConfig: RelayerI = { + apiKey: process.env.GELATO_KEY, +}; + +const relayerConfig: RelayerConfig = { + gelatoConfig: gelatoConfig, +}; + +const parifiSdk = new ParifiSdk(rpcConfig, subgraphConfig, relayerConfig, pythConfig); describe('Vault fetching logic from subgraph', () => { it('should return correct vault details', async () => { @@ -19,33 +38,35 @@ describe('Vault fetching logic from subgraph', () => { expect(vaults.length).not.toBe(0); }); -}); -describe('Vault fetching logic from subgraph', () => { - it('should return correct vault details', async () => { + it('should return correct Total Pool Value', async () => { await parifiSdk.init(); const data = await parifiSdk.subgraph.getTotalPoolsValue(); console.log(data); expect(data.totalPoolValue).not.toBe(0); }); -}); -describe('Vault fetching logic from subgraph', () => { - it('should return correct vault details', async () => { + it('should return correct user vault data', async () => { await parifiSdk.init(); const data = await parifiSdk.subgraph.getUserVaultDataByChain('0x30f06f86F107f9523f5b91A8E8AEB602b7b260BD'); console.log(data); expect(data.length).not.toBe(0); }); -}); -describe('Vault fetching logic from subgraph', () => { - it('should return correct vault details', async () => { + + it('should return correct user total pools vaule', async () => { await parifiSdk.init(); const data = await parifiSdk.subgraph.getUserTotalPoolsValue('0x30f06f86F107f9523f5b91A8E8AEB602b7b260BD'); console.log(data); expect(data.myTotalPoolValue).not.toBe(0); }); + + it('should return correct APR details', async () => { + await parifiSdk.init(); + const vaultId = '0x13a78809528b02ad5e7c42f39232d332761dfb1d'; + const data = await parifiSdk.subgraph.getVaultApr(vaultId); + console.log(data); + }); });