Skip to content

Commit

Permalink
chore(enzyme-finance): refactor enzyme using AppTokenTemplate (Zapper…
Browse files Browse the repository at this point in the history
  • Loading branch information
wpoulin committed Aug 8, 2022
1 parent e341029 commit 4c432eb
Showing 1 changed file with 85 additions and 103 deletions.
188 changes: 85 additions & 103 deletions src/apps/enzyme-finance/ethereum/enzyme-finance.vault.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import { Inject } from '@nestjs/common';
import BigNumber from 'bignumber.js';
import { gql } from 'graphql-request';
import _, { compact } from 'lodash';
import _ from 'lodash';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present';
import { getAppImg } 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 { DefaultDataProps } from '~position/display.interface';
import {
AppTokenTemplatePositionFetcher,
DataPropsStageParams,
DisplayPropsStageParams,
PriceStageParams,
} from '~position/template/app-token.template.position-fetcher';
import { Network } from '~types/network.interface';

import { EnzymeFinanceContractFactory } from '../contracts';
import { EnzymeFinanceContractFactory, EnzymeFinanceVault } from '../contracts';
import { ENZYME_FINANCE_DEFINITION } from '../enzyme-finance.definition';

type EnzymeFinanceVaultsResponse = {
funds: {
id: string;
}[];
};

const query = gql`
query fetchEnzymeVaults {
funds(first: 250, orderBy: investmentCount, orderDirection: desc) {
Expand All @@ -29,106 +25,92 @@ const query = gql`
}
`;

type EnzymeFinanceVaultsResponse = {
funds: {
id: string;
}[];
};

export type EnzymeFinanceVaultTokenDataProps = {
liquidity: number;
isActive: boolean;
};

const appId = ENZYME_FINANCE_DEFINITION.id;
const groupId = ENZYME_FINANCE_DEFINITION.groups.vault.id;
const network = Network.ETHEREUM_MAINNET;

@Register.TokenPositionFetcher({ appId, groupId, network })
export class EthereumEnzymeFinanceVaultTokenFetcher implements PositionFetcher<AppTokenPosition> {
export class EthereumEnzymeFinanceVaultTokenFetcher extends AppTokenTemplatePositionFetcher<EnzymeFinanceVault> {
appId = ENZYME_FINANCE_DEFINITION.id;
groupId = ENZYME_FINANCE_DEFINITION.groups.vault.id;
network = Network.ETHEREUM_MAINNET;

constructor(
@Inject(EnzymeFinanceContractFactory) private readonly enzymeFinanceContractFactory: EnzymeFinanceContractFactory,
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
) {}

@CacheOnInterval({
key: `studio:${appId}:${groupId}:${network}:addresses`,
timeout: 15 * 60 * 1000,
})
async getCachedPoolAddresses() {
@Inject(EnzymeFinanceContractFactory) private readonly contractFactory: EnzymeFinanceContractFactory,
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
) {
super(appToolkit);
}

async getAddresses(): Promise<string[]> {
const endpoint = `https://api.thegraph.com/subgraphs/name/enzymefinance/enzyme`;
const data = await this.appToolkit.helpers.theGraphHelper.request<EnzymeFinanceVaultsResponse>({ endpoint, query });
return data.funds.map(v => v);
return data.funds.map(v => v.id.toLowerCase());
}

async getPositions() {
const multicall = this.appToolkit.getMulticall(network);
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const poolAddresses = await this.getCachedPoolAddresses();

const tokens = await Promise.all(
poolAddresses.map(async vault => {
const vaultAddress = vault.id.toLowerCase();
const tokenContract = this.enzymeFinanceContractFactory.enzymeFinanceVault({ address: vaultAddress, network });
const [name, symbol, decimals, supplyRaw, trackedAssetAddresses] = await Promise.all([
multicall.wrap(tokenContract).name(),
multicall.wrap(tokenContract).symbol(),
multicall.wrap(tokenContract).decimals(),
multicall.wrap(tokenContract).totalSupply(),
multicall.wrap(tokenContract).getTrackedAssets(),
]);

const totalAssetUnderManagement = _.sum(
await Promise.all(
trackedAssetAddresses.map(async tokenAddressRaw => {
const tokenAddress = tokenAddressRaw.toLowerCase();
const uTokenContract = this.enzymeFinanceContractFactory.erc20({ address: tokenAddress, network });
const [tokenAmountRaw, decimals] = await Promise.all([
multicall.wrap(uTokenContract).balanceOf(vaultAddress),
multicall.wrap(uTokenContract).decimals(),
]);

const amount = Number(tokenAmountRaw) / 10 ** decimals;
const baseToken = baseTokens.find(v => v.address === tokenAddress);
if (!baseToken) return 0;

return baseToken.price * amount;
}),
),
);

const underlyingTokens = trackedAssetAddresses.map(token => {
return baseTokens.find(x => x.address === token.toLowerCase());
});

const supply = Number(supplyRaw) / 10 ** decimals;
// filtering out vaults without depositors
if (supply === 0) return null;
const pricePerShare = 1;
const price = totalAssetUnderManagement / supply;
const tokens = compact(underlyingTokens);
const liquidity = price * supply;

const label = name;
const secondaryLabel = buildDollarDisplayItem(price);
const images = [getAppImg(appId)];
const dataProps = { liquidity };
const displayProps = {
label,
secondaryLabel,
images,
statsItems: [{ label: 'Liquidity', value: buildDollarDisplayItem(liquidity) }],
};

const token: AppTokenPosition = {
type: ContractType.APP_TOKEN,
address: vaultAddress,
appId,
groupId,
network,
symbol,
decimals,
supply,
price,
pricePerShare,
tokens,
dataProps,
displayProps,
};

return token;
}),
getContract(address: string): EnzymeFinanceVault {
return this.contractFactory.enzymeFinanceVault({ network: this.network, address });
}

async getLabel({
contract,
}: DisplayPropsStageParams<EnzymeFinanceVault, EnzymeFinanceVaultTokenDataProps>): Promise<string> {
return contract.name();
}

async getUnderlyingTokenAddresses(contract: EnzymeFinanceVault) {
return (await contract.getTrackedAssets()).map(x => x.toLowerCase());
}

async getPrice({ appToken, contract }: PriceStageParams<EnzymeFinanceVault, DefaultDataProps>): Promise<number> {
const multicall = this.appToolkit.getMulticall(this.network);
const tokenSelector = this.appToolkit.getBaseTokenPriceSelector({ tags: { network, appId } });
const baseTokens = await tokenSelector.getAll({ network });

const decimalsRaw = await this.getDecimals(contract);
const supplyRaw = await this.getSupply(contract);
const decimals = new BigNumber(10).exponentiatedBy(decimalsRaw);
const supply = new BigNumber(supplyRaw.toString()).div(decimals);
const underlying = await this.getUnderlyingTokenAddresses(contract);

const totalAssetUnderManagement = _.sum(
await Promise.all(
underlying.map(async tokenAddressRaw => {
const tokenAddress = tokenAddressRaw.toLowerCase();
const uTokenContract = this.contractFactory.erc20({ address: tokenAddress, network });
const [tokenAmountRaw, decimals] = await Promise.all([
multicall.wrap(uTokenContract).balanceOf(appToken.address),
multicall.wrap(uTokenContract).decimals(),
]);

const amount = Number(tokenAmountRaw) / 10 ** decimals;
const baseToken = baseTokens.find(v => v.address === tokenAddress);
if (!baseToken) return 0;

return baseToken.price * amount;
}),
),
);

return compact(tokens);
return Number(supply) > 0 ? new BigNumber(totalAssetUnderManagement.toString()).div(supply).toNumber() : 0;
}

async getDataProps(opts: DataPropsStageParams<EnzymeFinanceVault, DefaultDataProps>): Promise<DefaultDataProps> {
const { appToken } = opts;
const liquidity = appToken.price * appToken.supply;
const isActive = appToken.supply > 0 ? true : false;

return { liquidity, isActive };
}
}

0 comments on commit 4c432eb

Please sign in to comment.