Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Commit

Permalink
feat(sturdy): Adds Ethereum version of Sturdy Finance (#574)
Browse files Browse the repository at this point in the history
  • Loading branch information
theonepichael committed Jun 7, 2022
1 parent 59b4cb0 commit 291a50e
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 23 deletions.
37 changes: 37 additions & 0 deletions 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,
},
]);
}
}
99 changes: 99 additions & 0 deletions 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<AppTokenPosition> {
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<VaultMonitoringResponse>(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);
}
}
26 changes: 26 additions & 0 deletions 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);
}
}
37 changes: 14 additions & 23 deletions src/apps/sturdy/fantom/sturdy.lending.token-fetcher.ts
Expand Up @@ -9,54 +9,45 @@ 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';
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<AppTokenPosition> {
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';
const data = await axios.get<VaultMonitoringResponse>(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<VaultMonitoringResponse>(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;
Expand Down Expand Up @@ -91,7 +82,7 @@ export class FantomSturdyLendingTokenFetcher implements PositionFetcher<AppToken
images: getImagesFromToken(underlyingTokens[0]),
statsItems: [
{
label: 'apy',
label: 'APY',
value: buildPercentageDisplayItem(data.base),
},
{
Expand Down
32 changes: 32 additions & 0 deletions src/apps/sturdy/helpers/constants.ts
@@ -0,0 +1,32 @@
import { Network } from '~types';

export 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;
}[];

export const TIMEOUT_DURATION = 15 * 60 * 1000;

/**
* Creates a unique primary key used for caching API responses
* @param appId The application's unique identifier
* @param groupId The application's unique group identifier
* @param network Network where the application is deployed
* @returns string
*/
export function cacheOnIntervalKeyCreationHelper(appId: string, groupId: string, network: Network): string {
return `studio:${appId}:${groupId}:${network}:addresses`;
}
1 change: 1 addition & 0 deletions src/apps/sturdy/sturdy.definition.ts
Expand Up @@ -22,6 +22,7 @@ export const STURDY_DEFINITION = appDefinition({
},

supportedNetworks: {
[Network.ETHEREUM_MAINNET]: [AppAction.VIEW],
[Network.FANTOM_OPERA_MAINNET]: [AppAction.VIEW],
},

Expand Down
6 changes: 6 additions & 0 deletions src/apps/sturdy/sturdy.module.ts
Expand Up @@ -2,6 +2,9 @@ import { Register } from '~app-toolkit/decorators';
import { AbstractApp } from '~app/app.dynamic-module';

import { SturdyContractFactory } from './contracts';
import { EthereumSturdyBalanceFetcher } from './ethereum/sturdy.balance-fetcher';
import { EthereumSturdyLendingTokenFetcher } from './ethereum/sturdy.lending.token-fetcher';
import { EthereumSturdyTvlFetcher } from './ethereum/sturdy.tvl-fetcher';
import { FantomSturdyBalanceFetcher } from './fantom/sturdy.balance-fetcher';
import { FantomSturdyLendingTokenFetcher } from './fantom/sturdy.lending.token-fetcher';
import { FantomSturdyTvlFetcher } from './fantom/sturdy.tvl-fetcher';
Expand All @@ -10,6 +13,9 @@ import { SturdyAppDefinition, STURDY_DEFINITION } from './sturdy.definition';
@Register.AppModule({
appId: STURDY_DEFINITION.id,
providers: [
EthereumSturdyBalanceFetcher,
EthereumSturdyLendingTokenFetcher,
EthereumSturdyTvlFetcher,
FantomSturdyBalanceFetcher,
FantomSturdyLendingTokenFetcher,
FantomSturdyTvlFetcher,
Expand Down

0 comments on commit 291a50e

Please sign in to comment.