diff --git a/src/apps/amp/amp.module.ts b/src/apps/amp/amp.module.ts index 2b2efb0b3..f38763509 100644 --- a/src/apps/amp/amp.module.ts +++ b/src/apps/amp/amp.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { AbstractApp } from '~app/app.dynamic-module'; +import { AmpStakingResolver } from './common/amp.staking-resolver'; import { AmpContractFactory } from './contracts'; import { EthereumAmpFarmContractPositionFetcher } from './ethereum/amp.farm.contract-position-fetcher'; @Module({ - providers: [AmpContractFactory, EthereumAmpFarmContractPositionFetcher], + providers: [AmpContractFactory, AmpStakingResolver, EthereumAmpFarmContractPositionFetcher], }) export class AmpAppModule extends AbstractApp() {} diff --git a/src/apps/amp/common/amp.staking-resolver.ts b/src/apps/amp/common/amp.staking-resolver.ts new file mode 100644 index 000000000..b93d90dfd --- /dev/null +++ b/src/apps/amp/common/amp.staking-resolver.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import Axios, { AxiosError } from 'axios'; + +import { Cache } from '~cache/cache.decorator'; + +interface AmpStakingApiData { + apps: { + displayName: string; + ampPartition: string; + apy: string; + }[]; +} + +interface AmpStakingBalanceData { + address: string; + supplyTotal: string; + rewardTotal: string; +} + +@Injectable() +export class AmpStakingResolver { + @Cache({ + key: `studio:amp:staking:staking-data`, + ttl: 15 * 60, // 15 minutes + }) + private async getPoolApyData() { + const url = 'https://api.capacity.production.flexa.network/apps'; + const { data } = await Axios.get(url, { + headers: { Accept: 'application/vnd.flexa.capacity.v1+json' }, + }); + + return data.apps; + } + + @Cache({ + key: (address: string) => `studio:amp:staking:balance-data:${address}`, + ttl: 5 * 60, // 15 minutes + }) + private async getBalanceData(address: string) { + const url = `https://api.capacity.production.flexa.network/accounts/${address}/totals`; + const data = await Axios.get(url, { + headers: { Accept: 'application/vnd.flexa.capacity.v1+json' }, + }) + .then(({ data }) => data) + .catch(err => { + if ((err as AxiosError).response?.data.error === 'Address not found') + return { address, supplyTotal: '0', rewardTotal: '0' } as AmpStakingBalanceData; + throw err; + }); + + return data; + } + + async getBalance(address: string) { + const balance = await this.getBalanceData(address); + + return { + address: balance.address, + supplyTotal: Number(balance.supplyTotal), + rewardTotal: Number(balance.rewardTotal), + }; + } + + async getPoolApys(poolName: string) { + const poolApys = await this.getPoolApyData(); + + const poolApy = poolApys.find(x => x.displayName == poolName); + if (!poolApy) return 0; + + return poolApy.apy; + } +} diff --git a/src/apps/amp/ethereum/amp.farm.contract-position-fetcher.ts b/src/apps/amp/ethereum/amp.farm.contract-position-fetcher.ts index 746e74ce5..f49112d7b 100644 --- a/src/apps/amp/ethereum/amp.farm.contract-position-fetcher.ts +++ b/src/apps/amp/ethereum/amp.farm.contract-position-fetcher.ts @@ -1,12 +1,8 @@ import { Inject } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import axios, { AxiosError } from 'axios'; import { BigNumberish } from 'ethers'; -import { sortBy } from 'lodash'; import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator'; -import { Cache } from '~cache/cache.decorator'; import { MetaType } from '~position/position.interface'; import { ContractPositionTemplatePositionFetcher } from '~position/template/contract-position.template.position-fetcher'; import { @@ -14,25 +10,25 @@ import { GetTokenBalancesParams, } from '~position/template/contract-position.template.types'; +import { AmpStakingResolver } from '../common/amp.staking-resolver'; import { AmpContractFactory, AmpStaking } from '../contracts'; -type DepositedAmpResponse = { - supplyTotal: string; - rewardTotal: string; -}; - @PositionTemplate() export class EthereumAmpFarmContractPositionFetcher extends ContractPositionTemplatePositionFetcher { groupLabel = 'Flexa Capacity'; constructor( @Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit, - @Inject(ConfigService) protected readonly configService: ConfigService, + @Inject(AmpStakingResolver) protected readonly ampStakingResolver: AmpStakingResolver, @Inject(AmpContractFactory) protected readonly contractFactory: AmpContractFactory, ) { super(appToolkit); } + getContract(address: string): AmpStaking { + return this.contractFactory.ampStaking({ address, network: this.network }); + } + async getDefinitions(): Promise { return [{ address: '0x706d7f8b3445d8dfc790c524e3990ef014e7c578' }]; } @@ -50,76 +46,17 @@ export class EthereumAmpFarmContractPositionFetcher extends ContractPositionTemp metaType: MetaType.LOCKED, address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2', network: this.network, - symbol: 'AMP Rewards', }, ]; } - getContract(address: string): AmpStaking { - return this.contractFactory.ampStaking({ address, network: this.network }); - } - @Cache({ - instance: 'business', - key: (address: string) => `apps-v3:balance:ethereum:amp:api-data:${address}`, - ttl: 15 * 60, // 15 minutes - }) - async getAddressBalances(address: string) { - const axiosInstance = axios.create({ - baseURL: 'https://api.capacity.production.flexa.network', - headers: { - Accept: 'application/vnd.flexa.capacity.v1+json', - }, - }); - return axiosInstance - .get(`/accounts/${address}/totals`) - .then(({ data }) => data) - .catch(err => { - if ((err as AxiosError).response?.data.error === 'Address not found') - return { supplyTotal: '0', rewardTotal: '0' } as DepositedAmpResponse; - throw err; - }); - } - async getTokenBalancesPerPosition({ address }: GetTokenBalancesParams): Promise { - const { supplyTotal, rewardTotal } = await this.getAddressBalances(address); - return rewardTotal > supplyTotal ? [] : [supplyTotal, rewardTotal]; - } - @Cache({ - instance: 'business', - key: () => `apps-v3:balance:ethereum:amp:api-data`, - ttl: 15 * 60, // 15 minutes - }) - async getPoolApys() { - const capacityResponse = axios.create({ - baseURL: 'https://api.capacity.production.flexa.network', - headers: { - Accept: 'application/vnd.flexa.capacity.v1+json', - }, - }); - return capacityResponse - .get('/apps') - .then(function (response) { - let apyRangeLabel; - const data = response.data; - if (data.apps) { - for (const i in data.apps) { - data.apps[i].numApy = Number(data.apps[i].apy); - } - const sortedApps = sortBy(data.apps, 'numApy'); - const lowestApy = sortedApps[0].apy === '0' ? sortedApps[1].apy : sortedApps[0].apy; - const highestApy = sortedApps[sortedApps.length - 1].apy; - apyRangeLabel = 'Staked Amp (' + highestApy + '% to ' + lowestApy + '% APY)'; - } else { - apyRangeLabel = 'Staked Amp'; - } - return apyRangeLabel; - }) - .catch(function () { - return 'Staked Amp'; - }); + const { supplyTotal, rewardTotal } = await this.ampStakingResolver.getBalance(address); + return rewardTotal > supplyTotal ? [0, 0] : [supplyTotal, rewardTotal]; } async getLabel() { - return await this.getPoolApys(); + const apy = await this.ampStakingResolver.getPoolApys(this.network); + return `Amp Staking ${apy}%`; } } diff --git a/src/apps/gamma-strategies/helpers/gamma-strategies.api.ts b/src/apps/gamma-strategies/helpers/gamma-strategies.api.ts index 8e6ee621e..739af5d6b 100644 --- a/src/apps/gamma-strategies/helpers/gamma-strategies.api.ts +++ b/src/apps/gamma-strategies/helpers/gamma-strategies.api.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import Axios from 'axios'; -import { map, merge } from 'lodash' +import { map, merge } from 'lodash'; import { Cache } from '~cache/cache.decorator'; import { Network } from '~types/network.interface'; @@ -9,17 +9,19 @@ export type GammaApiTokensResponse = Record; @Injectable() export class GammaApiHelper { - constructor() { } + constructor() {} @Cache({ key: (network: Network) => `studio:gamma:${network}:vaults`, ttl: 60 * 60, // 12 hours }) async getVaultDefinitionsData(urls: string[]) { - const data = await Promise.all(map(urls, async (url) => { - const { data } = await Axios.get(url); - return data - })) - return merge(...data as [GammaApiTokensResponse, GammaApiTokensResponse]) + const data = await Promise.all( + map(urls, async url => { + const { data } = await Axios.get(url); + return data; + }), + ); + return merge(...(data as [GammaApiTokensResponse, GammaApiTokensResponse])); } }