From 4847121180a12f76e2e9e90bd9b39fa19aa0cf7e Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Fri, 14 Apr 2023 14:36:49 +0200 Subject: [PATCH] Mp 2539 vaults api (#156) * add basic endpoint for vaultConfigs * implement apy for vaults --- src/components/Earn/Overview.tsx | 3 + src/constants/vaults.ts | 38 +++++++++ src/pages/api/vaults/index.ts | 128 ++++++++++++++++++++++++++++++- src/types/interfaces/vaults.d.ts | 34 ++++++++ src/utils/api.ts | 12 ++- src/utils/parsers.ts | 4 + src/utils/vaults.ts | 5 ++ 7 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 src/constants/vaults.ts create mode 100644 src/types/interfaces/vaults.d.ts create mode 100644 src/utils/vaults.ts diff --git a/src/components/Earn/Overview.tsx b/src/components/Earn/Overview.tsx index 653b0c18..a48471f8 100644 --- a/src/components/Earn/Overview.tsx +++ b/src/components/Earn/Overview.tsx @@ -3,8 +3,11 @@ import { Suspense } from 'react' import Card from 'components/Card' import Loading from 'components/Loading' import { Text } from 'components/Text' +import { getVaults } from 'utils/api' async function Content(props: PageProps) { + const vaults = await getVaults() + const address = props.params.address if (!address) diff --git a/src/constants/vaults.ts b/src/constants/vaults.ts new file mode 100644 index 00000000..bbf334fa --- /dev/null +++ b/src/constants/vaults.ts @@ -0,0 +1,38 @@ +export const VAULTS: VaultMetaData[] = [ + { + address: 'osmo108q2krqr0y9g0rtesenvsw68sap2xefelwwjs0wedyvdl0cmrntqvllfjk', + name: 'OSMO-ATOM', + lockup: { + duration: 14, + timeframe: 'day', + }, + provider: 'Apollo', + denoms: { + primary: 'uosmo', + secondary: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', + lp: 'gamm/pool/1', + }, + symbols: { + primary: 'OSMO', + secondary: 'ATOM', + }, + }, + { + address: 'osmo1g5hryv0gp9dzlchkp3yxk8fmcf5asjun6cxkvyffetqzkwmvy75qfmeq3f', + name: 'OSMO - JUNO', + lockup: { + duration: 14, + timeframe: 'day', + }, + provider: 'Apollo', + denoms: { + primary: 'uosmo', + secondary: 'ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED', + lp: 'gamm/pool/497', + }, + symbols: { + primary: 'OSMO', + secondary: 'JUNO', + }, + }, +] diff --git a/src/pages/api/vaults/index.ts b/src/pages/api/vaults/index.ts index 25a53c44..8794dff5 100644 --- a/src/pages/api/vaults/index.ts +++ b/src/pages/api/vaults/index.ts @@ -2,6 +2,12 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { NextApiRequest, NextApiResponse } from 'next' import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' +import { + ArrayOfVaultInfoResponse, + VaultBaseForString, +} from 'types/generated/mars-credit-manager/MarsCreditManager.types' +import { VAULTS } from 'constants/vaults' +import { convertAprToApy } from 'utils/parsers' export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER) { @@ -9,13 +15,127 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } const client = await CosmWasmClient.connect(ENV.URL_RPC) - const data = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, { - vaults_info: { limit: 5, start_after: undefined }, + const $vaultConfigs = getVaultConfigs(client) + const $aprs = getAprs() + const vaults: Vault[] = await Promise.all([$vaultConfigs, $aprs]).then(([vaultConfigs, aprs]) => { + return vaultConfigs.map((vaultConfig) => { + const apr = aprs.find((apr) => apr.address === vaultConfig.address) + if (apr) { + return { + ...vaultConfig, + apy: convertAprToApy(apr.apr, 365), + } + } + return { + ...vaultConfig, + apy: null, + } + }) }) - if (data) { - return res.status(200).json(data) + if (vaults) { + return res.status(200).json(vaults) } return res.status(404) } + +async function getVaultConfigs(client: CosmWasmClient, startAfter?: VaultBaseForString) { + let data: VaultConfig[] = [] + + const getBatch = async (startAfter?: VaultBaseForString) => { + if (!ENV.ADDRESS_CREDIT_MANAGER) return + + const batch: ArrayOfVaultInfoResponse = await client.queryContractSmart( + ENV.ADDRESS_CREDIT_MANAGER, + { + vaults_info: { limit: 4, start_after: startAfter }, + }, + ) + + const batchProcessed = batch?.map((vaultInfo) => { + return { + address: vaultInfo.vault.address, + cap: { + denom: vaultInfo.config.deposit_cap.denom, + used: Number(vaultInfo.utilization.amount), + max: Number(vaultInfo.config.deposit_cap.amount), + }, + ltv: { + max: Number(vaultInfo.config.max_ltv), + liq: Number(vaultInfo.config.liquidation_threshold), + }, + } as VaultConfig + }) + + data = [...data, ...batchProcessed] + + if (batch.length === 4) { + await getBatch({ + address: batchProcessed[batchProcessed.length - 1].address, + } as VaultBaseForString) + } + } + + await getBatch() + + return VAULTS.map((vaultMetaData) => { + const vaultInfo = data.find((vault) => vault.address === vaultMetaData.address) + + return { + ...vaultMetaData, + ...vaultInfo, + } as VaultConfig + }) +} + +interface FlatApr { + contract_address: string + apr: { type: string; value: number | string }[] + fees: { type: string; value: number | string }[] +} + +interface NestedApr { + contract_address: string + apr: { + aprs: { type: string; value: number | string }[] + fees: { type: string; value: number | string }[] + } +} + +async function getAprs() { + const APOLLO_URL = 'https://api.apollo.farm/api/vault_infos/v2/osmo-test-4' + + try { + const response = await fetch(APOLLO_URL) + + if (response.ok) { + const data: FlatApr[] | NestedApr[] = await response.json() + + const newAprs = data.map((aprData) => { + try { + const apr = aprData as FlatApr + const aprTotal = apr.apr.reduce((prev, curr) => Number(curr.value) + prev, 0) + const feeTotal = apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0) + + const finalApr = aprTotal + feeTotal + + return { address: aprData.contract_address, apr: finalApr } + } catch { + const apr = aprData as NestedApr + const aprTotal = apr.apr.aprs.reduce((prev, curr) => Number(curr.value) + prev, 0) + const feeTotal = apr.apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0) + + const finalApr = aprTotal + feeTotal + return { address: aprData.contract_address, apr: finalApr } + } + }) + + return newAprs + } + + return [] + } catch { + return [] + } +} diff --git a/src/types/interfaces/vaults.d.ts b/src/types/interfaces/vaults.d.ts new file mode 100644 index 00000000..fe3fc910 --- /dev/null +++ b/src/types/interfaces/vaults.d.ts @@ -0,0 +1,34 @@ +interface VaultMetaData { + address: string + name: string + lockup: { + duration: number + timeframe: string + } + provider: string + denoms: { + primary: string + secondary: string + lp: string + } + symbols: { + primary: string + secondary: string + } +} + +interface VaultConfig extends VaultMetaData { + ltv: { + max: number + liq: number + } + cap: { + denom: string + used: numnber + max: number + } +} + +interface Vault extends VaultConfig { + apy: number | null +} diff --git a/src/utils/api.ts b/src/utils/api.ts index 094faee8..38451e0f 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -9,6 +9,7 @@ export enum Endpoints { ACCOUNT_DEBTS = '/accounts/{accountId}/debts', MARKETS_BORROW = '/markets/borrow', PRICES = '/prices', + VAULTS = '/vaults', WALLET_BALANCES = '/wallets/{address}/balances', } @@ -26,9 +27,12 @@ export function getEndpoint(endpoint: Endpoints, props?: ParamProps) { return returnEndpoint } -export async function callAPI(endpoint: string): Promise { +export async function callAPI(endpoint: string, cache?: RequestCache): Promise { const response = await fetch(`${ENV.URL_API}${endpoint}${VERCEL_BYPASS}`, { - cache: 'no-store', + cache: cache ? cache : 'no-store', + next: { + revalidate: 30, + }, }) return response.json() as T @@ -60,6 +64,10 @@ export async function getPrices() { return callAPI(getEndpoint(Endpoints.PRICES)) } +export async function getVaults() { + return callAPI(getEndpoint(Endpoints.VAULTS), 'default') +} + export async function getWalletBalancesSWR(url: string) { return callAPI(url) } diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index a9db75d8..5c130441 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -9,3 +9,7 @@ export function isNumber(value: unknown) { return false } + +export const convertAprToApy = (apr: number, numberOfCompoundingPeriods: number): number => { + return ((1 + apr / 100 / numberOfCompoundingPeriods) ** numberOfCompoundingPeriods - 1) * 100 +} diff --git a/src/utils/vaults.ts b/src/utils/vaults.ts new file mode 100644 index 00000000..353ade50 --- /dev/null +++ b/src/utils/vaults.ts @@ -0,0 +1,5 @@ +import { VAULTS } from 'constants/vaults' + +export function getVaultMetaData(address: string) { + return VAULTS.find((vault) => vault.address === address) +}