From 291a50e9f1b54887ff8bd258f54c848e23c9174e Mon Sep 17 00:00:00 2001 From: The Ol' Dirty Bubble <106809061+theonepichael@users.noreply.github.com> Date: Tue, 7 Jun 2022 08:38:59 -0400 Subject: [PATCH] feat(sturdy): Adds Ethereum version of Sturdy Finance (#574) --- .../sturdy/ethereum/sturdy.balance-fetcher.ts | 37 +++++++ .../ethereum/sturdy.lending.token-fetcher.ts | 99 +++++++++++++++++++ .../sturdy/ethereum/sturdy.tvl-fetcher.ts | 26 +++++ .../fantom/sturdy.lending.token-fetcher.ts | 37 +++---- src/apps/sturdy/helpers/constants.ts | 32 ++++++ src/apps/sturdy/sturdy.definition.ts | 1 + src/apps/sturdy/sturdy.module.ts | 6 ++ 7 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 src/apps/sturdy/ethereum/sturdy.balance-fetcher.ts create mode 100644 src/apps/sturdy/ethereum/sturdy.lending.token-fetcher.ts create mode 100644 src/apps/sturdy/ethereum/sturdy.tvl-fetcher.ts create mode 100644 src/apps/sturdy/helpers/constants.ts diff --git a/src/apps/sturdy/ethereum/sturdy.balance-fetcher.ts b/src/apps/sturdy/ethereum/sturdy.balance-fetcher.ts new file mode 100644 index 000000000..3cfaee150 --- /dev/null +++ b/src/apps/sturdy/ethereum/sturdy.balance-fetcher.ts @@ -0,0 +1,37 @@ +import { Inject } from '@nestjs/common'; + +import { TokenBalanceHelper } from '~app-toolkit'; +import { Register } from '~app-toolkit/decorators'; +import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present'; +import { BalanceFetcher } from '~balance/balance-fetcher.interface'; +import { Network } from '~types/network.interface'; + +import { STURDY_DEFINITION } from '../sturdy.definition'; + +const network = Network.ETHEREUM_MAINNET; +const appId = STURDY_DEFINITION.id; + +@Register.BalanceFetcher(STURDY_DEFINITION.id, network) +export class EthereumSturdyBalanceFetcher implements BalanceFetcher { + constructor(@Inject(TokenBalanceHelper) private readonly tokenBalanceHelper: TokenBalanceHelper) {} + + private async getLendingTokenBalances(address: string) { + return this.tokenBalanceHelper.getTokenBalances({ + address, + appId, + groupId: STURDY_DEFINITION.groups.lending.id, + network, + }); + } + + async getBalances(address: string) { + const [lendingTokenBalances] = await Promise.all([this.getLendingTokenBalances(address)]); + + return presentBalanceFetcherResponse([ + { + label: 'Lending', + assets: lendingTokenBalances, + }, + ]); + } +} diff --git a/src/apps/sturdy/ethereum/sturdy.lending.token-fetcher.ts b/src/apps/sturdy/ethereum/sturdy.lending.token-fetcher.ts new file mode 100644 index 000000000..a0beac59c --- /dev/null +++ b/src/apps/sturdy/ethereum/sturdy.lending.token-fetcher.ts @@ -0,0 +1,99 @@ +import { Inject } from '@nestjs/common'; +import axios from 'axios'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { ZERO_ADDRESS } from '~app-toolkit/constants/address'; +import { Register } from '~app-toolkit/decorators'; +import { + buildNumberDisplayItem, + buildPercentageDisplayItem, +} from '~app-toolkit/helpers/presentation/display-item.present'; +import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present'; +import { CacheOnInterval } from '~cache/cache-on-interval.decorator'; +import { ContractType } from '~position/contract.interface'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { AppTokenPosition } from '~position/position.interface'; +import { BaseToken } from '~position/token.interface'; +import { Network } from '~types/network.interface'; + +import { SturdyContractFactory } from '../contracts'; +import { VaultMonitoringResponse, cacheOnIntervalKeyCreationHelper, TIMEOUT_DURATION } from '../helpers/constants'; +import { STURDY_DEFINITION } from '../sturdy.definition'; + +const appId = STURDY_DEFINITION.id; +const groupId = STURDY_DEFINITION.groups.lending.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.TokenPositionFetcher({ appId, groupId, network }) +export class EthereumSturdyLendingTokenFetcher implements PositionFetcher { + constructor( + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + @Inject(SturdyContractFactory) private readonly sturdyContractFactory: SturdyContractFactory, + ) {} + + @CacheOnInterval({ + key: cacheOnIntervalKeyCreationHelper(appId, groupId, network), + timeout: TIMEOUT_DURATION, + }) + private async getVaultMonitoringData() { + const endpoint = 'https://us-central1-stu-dashboard-a0ba2.cloudfunctions.net/getVaultMonitoring?chain=ethereum'; + const data = await axios.get(endpoint).then(res => res.data); + return data; + } + + async getPositions() { + const multicall = this.appToolkit.getMulticall(network); + const baseTokens = await this.appToolkit.getBaseTokenPrices(network); + const ethToken = baseTokens.find(t => t.address === ZERO_ADDRESS); + if (!ethToken) return []; + + const tokenData = await this.getVaultMonitoringData(); + + const tokens = tokenData.map(async data => { + const symbol = data.tokens; + const underlyingTokens: BaseToken[] = []; + + const contract = this.sturdyContractFactory.sturdyToken({ address: data.address, network }); + const underlyingTokenAddress = await multicall + .wrap(contract) + .UNDERLYING_ASSET_ADDRESS() + .then(v => v.toLowerCase()); + const underlyingToken = baseTokens.find(t => t.address === underlyingTokenAddress); + if (underlyingToken) underlyingTokens.push(underlyingToken); + + const token: AppTokenPosition = { + type: ContractType.APP_TOKEN, + appId, + groupId, + address: data.address, + network, + symbol, + decimals: data.decimals, + supply: data.supply, + pricePerShare: 1, + price: underlyingTokens[0].price, + tokens: underlyingTokens, + dataProps: { + apy: data.base, + tvl: data.tvl, + }, + displayProps: { + label: symbol, + images: getImagesFromToken(underlyingTokens[0]), + statsItems: [ + { + label: 'APY', + value: buildPercentageDisplayItem(data.base), + }, + { + label: 'Liquidity', + value: buildNumberDisplayItem(data.tvl), + }, + ], + }, + }; + return token; + }); + return Promise.all(tokens); + } +} diff --git a/src/apps/sturdy/ethereum/sturdy.tvl-fetcher.ts b/src/apps/sturdy/ethereum/sturdy.tvl-fetcher.ts new file mode 100644 index 000000000..3a7f98712 --- /dev/null +++ b/src/apps/sturdy/ethereum/sturdy.tvl-fetcher.ts @@ -0,0 +1,26 @@ +import { Inject } from '@nestjs/common'; +import { sumBy } from 'lodash'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { Register } from '~app-toolkit/decorators'; +import { TvlFetcher } from '~stats/tvl/tvl-fetcher.interface'; +import { Network } from '~types/network.interface'; + +import { STURDY_DEFINITION } from '../sturdy.definition'; + +const appId = STURDY_DEFINITION.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.TvlFetcher({ appId, network }) +export class EthereumSturdyTvlFetcher implements TvlFetcher { + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + + async getTvl() { + const lendingTokens = await this.appToolkit.getAppTokenPositions({ + appId, + groupIds: [STURDY_DEFINITION.groups.lending.id], + network, + }); + return sumBy(lendingTokens, v => v.supply * v.price); + } +} diff --git a/src/apps/sturdy/fantom/sturdy.lending.token-fetcher.ts b/src/apps/sturdy/fantom/sturdy.lending.token-fetcher.ts index 01a121d3a..87d27e3cd 100644 --- a/src/apps/sturdy/fantom/sturdy.lending.token-fetcher.ts +++ b/src/apps/sturdy/fantom/sturdy.lending.token-fetcher.ts @@ -9,6 +9,7 @@ import { buildPercentageDisplayItem, } from '~app-toolkit/helpers/presentation/display-item.present'; import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present'; +import { CacheOnInterval } from '~cache/cache-on-interval.decorator'; import { ContractType } from '~position/contract.interface'; import { PositionFetcher } from '~position/position-fetcher.interface'; import { AppTokenPosition } from '~position/position.interface'; @@ -16,30 +17,13 @@ import { BaseToken } from '~position/token.interface'; import { Network } from '~types/network.interface'; import { SturdyContractFactory } from '../contracts'; +import { VaultMonitoringResponse, cacheOnIntervalKeyCreationHelper, TIMEOUT_DURATION } from '../helpers/constants'; import { STURDY_DEFINITION } from '../sturdy.definition'; const appId = STURDY_DEFINITION.id; const groupId = STURDY_DEFINITION.groups.lending.id; const network = Network.FANTOM_OPERA_MAINNET; -type VaultMonitoringResponse = { - chain: string; - tokens: string; - decimals: number; - address: string; - supply: number; - price: number; - base: number; - reward: number; - rewards: { - CRV: number; - CVX: number; - }; - url: number; - tvl: number; - active: boolean; -}[]; - @Register.TokenPositionFetcher({ appId, groupId, network }) export class FantomSturdyLendingTokenFetcher implements PositionFetcher { constructor( @@ -47,16 +31,23 @@ export class FantomSturdyLendingTokenFetcher implements PositionFetcher(endpoint).then(res => res.data); + return data; + } + async getPositions() { const multicall = this.appToolkit.getMulticall(network); const baseTokens = await this.appToolkit.getBaseTokenPrices(network); const ethToken = baseTokens.find(t => t.address === ZERO_ADDRESS); if (!ethToken) return []; - const endpoint = 'https://us-central1-stu-dashboard-a0ba2.cloudfunctions.net/getVaultMonitoring'; - const tokenData = (await axios.get(endpoint).then(v => v.data)).filter( - data => data.chain === 'ftm', - ); + const tokenData = await this.getVaultMonitoringData(); const tokens = tokenData.map(async data => { const symbol = data.tokens; @@ -91,7 +82,7 @@ export class FantomSturdyLendingTokenFetcher implements PositionFetcher