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

feat(tenderize): integrate tenderTokens and swapTokens #882

Merged
merged 3 commits into from Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/apps/tenderize/arbitrum/tenderize.balance-fetcher.ts
@@ -0,0 +1,52 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
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 { TENDERIZE_DEFINITION } from '../tenderize.definition';

const network = Network.ARBITRUM_MAINNET;

@Register.BalanceFetcher(TENDERIZE_DEFINITION.id, network)
export class ArbitrumTenderizeBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {}

async getTenderTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
appId: TENDERIZE_DEFINITION.id,
groupId: TENDERIZE_DEFINITION.groups.tender.id,
network,
});
}

async getSwapTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
appId: TENDERIZE_DEFINITION.id,
groupId: TENDERIZE_DEFINITION.groups.swap.id,
network,
});
}

async getBalances(address: string) {
const [tenderTokenBalances, swapTokenBalances] = await Promise.all([
this.getTenderTokenBalances(address),
this.getSwapTokenBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'TenderTokens',
assets: tenderTokenBalances,
},
{
label: 'SwapTokens',
assets: swapTokenBalances,
},
]);
}
}
82 changes: 82 additions & 0 deletions src/apps/tenderize/arbitrum/tenderize.swap.token-fetcher.ts
@@ -0,0 +1,82 @@
import { Inject } from '@nestjs/common';

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 { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { ContractType } from '~position/contract.interface';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { TenderizeContractFactory } from '../contracts';
import { arbitrumEndpoint } from '../helpers/constants';
import { ConfigResponse, configQuery } from '../helpers/queries';
import { TENDERIZE_DEFINITION } from '../tenderize.definition';

const appId = TENDERIZE_DEFINITION.id;
const groupId = TENDERIZE_DEFINITION.groups.swap.id;
const network = Network.ARBITRUM_MAINNET;

@Register.TokenPositionFetcher({ appId, groupId, network })
export class ArbitrumTenderizeSwapTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(TenderizeContractFactory) private readonly tenderizeContractFactory: TenderizeContractFactory,
) {}

async getPosition(address: string, steak: string, tenderAddress: string) {
const multicall = this.appToolkit.getMulticall(network);
const contract = this.tenderizeContractFactory.erc20({ address, network });
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const underlyingToken = baseTokens.find(v => v.address === steak)!;
const tenderTokens = await this.appToolkit.getAppTokenPositions({
appId: TENDERIZE_DEFINITION.id,
groupIds: [TENDERIZE_DEFINITION.groups.tender.id],
network: Network.ARBITRUM_MAINNET,
});
const tenderToken = tenderTokens.find(v => v.address === tenderAddress)!;

const [symbol, decimals, totalSupply] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
]);
const supply = Number(totalSupply) / 10 ** decimals;
const price = underlyingToken.price;
const token: AppTokenPosition = {
address,
network,
appId,
groupId,
symbol,
decimals,
supply,
tokens: [tenderToken, underlyingToken],
dataProps: {},
pricePerShare: [0.5, 0.5],
price,
type: ContractType.APP_TOKEN,
displayProps: {
label: `${getLabelFromToken(tenderToken)} / ${getLabelFromToken(underlyingToken)}`,
secondaryLabel: buildDollarDisplayItem(price),
images: [],
statsItems: [],
},
};

return token;
}

async getPositions() {
const data = await this.appToolkit.helpers.theGraphHelper.request<ConfigResponse>({
endpoint: arbitrumEndpoint,
query: configQuery,
});

const positions = await Promise.all(
data.configs.map(async config => await this.getPosition(config.lpToken, config.steak, config.tenderToken)),
);
return positions;
}
}
86 changes: 86 additions & 0 deletions src/apps/tenderize/arbitrum/tenderize.tender.token-fetcher.ts
@@ -0,0 +1,86 @@
import { Inject } from '@nestjs/common';
import Axios from 'axios';

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 { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { ContractType } from '~position/contract.interface';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { TenderizeContractFactory } from '../contracts';
import { arbitrumEndpoint } from '../helpers/constants';
import { tenderTokenFetcherQuery, TenderTokenFetcherResponse } from '../helpers/queries';
import { APYResponse } from '../helpers/types';
import { TENDERIZE_DEFINITION } from '../tenderize.definition';

const appId = TENDERIZE_DEFINITION.id;
const groupId = TENDERIZE_DEFINITION.groups.tender.id;
const network = Network.ARBITRUM_MAINNET;

@Register.TokenPositionFetcher({ appId, groupId, network })
export class ArbitrumTenderizeTenderTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(TenderizeContractFactory) private readonly tenderizeContractFactory: TenderizeContractFactory,
) {}

async getPosition(address: string, steak: string, virtualPrice: string, apy: string) {
const multicall = this.appToolkit.getMulticall(network);
const contract = this.tenderizeContractFactory.erc20({ address, network });
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const underlyingToken = baseTokens.find(v => v.address === steak)!;

const [symbol, decimals, totalSupply] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
]);
const supply = Number(totalSupply) / 10 ** decimals;
const price = underlyingToken.price * (Number(virtualPrice) / 10 ** decimals);
const token: AppTokenPosition = {
address,
network,
appId,
groupId,
symbol,
decimals,
supply,
tokens: [],
dataProps: {},
pricePerShare: Number(virtualPrice) / 10 ** decimals,
price,
type: ContractType.APP_TOKEN,
displayProps: {
label: symbol,
secondaryLabel: buildDollarDisplayItem(price),
tertiaryLabel: `${apy}% APY`,
images: getImagesFromToken(underlyingToken),
statsItems: [],
},
};

return token;
}

async getPositions() {
const data = await this.appToolkit.helpers.theGraphHelper.request<TenderTokenFetcherResponse>({
endpoint: arbitrumEndpoint,
query: tenderTokenFetcherQuery,
});

const { data: apyData } = await Axios.get<APYResponse>('https://www.tenderize.me/api/apy');
const apyArr = Object.values(apyData);
const positions = await Promise.all(
data.configs.map(async config => {
const apy = apyArr.find(item => item.subgraphId === config.id)?.apy;
const virtualPrice =
data.tenderSwaps.find(item => item.id === config.id)?.virtualPrice ?? '1000000000000000000';
return await this.getPosition(config.tenderToken, config.steak, virtualPrice, apy ?? '0');
}),
);
return positions;
}
}
15 changes: 15 additions & 0 deletions src/apps/tenderize/contracts/index.ts
@@ -0,0 +1,15 @@
import { Injectable, Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { ContractFactory } from '~contract/contracts';
import { Network } from '~types/network.interface';

// eslint-disable-next-line
type ContractOpts = { address: string; network: Network };

@Injectable()
export class TenderizeContractFactory extends ContractFactory {
constructor(@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit) {
super((network: Network) => appToolkit.getNetworkProvider(network));
}
}
52 changes: 52 additions & 0 deletions src/apps/tenderize/ethereum/tenderize.balance-fetcher.ts
@@ -0,0 +1,52 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
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 { TENDERIZE_DEFINITION } from '../tenderize.definition';

const network = Network.ETHEREUM_MAINNET;

@Register.BalanceFetcher(TENDERIZE_DEFINITION.id, network)
export class EthereumTenderizeBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {}

async getTenderTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
appId: TENDERIZE_DEFINITION.id,
groupId: TENDERIZE_DEFINITION.groups.tender.id,
network,
});
}

async getSwapTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
appId: TENDERIZE_DEFINITION.id,
groupId: TENDERIZE_DEFINITION.groups.swap.id,
network,
});
}

async getBalances(address: string) {
const [tenderTokenBalances, swapTokenBalances] = await Promise.all([
this.getTenderTokenBalances(address),
this.getSwapTokenBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'TenderTokens',
assets: tenderTokenBalances,
},
{
label: 'SwapTokens',
assets: swapTokenBalances,
},
]);
}
}
82 changes: 82 additions & 0 deletions src/apps/tenderize/ethereum/tenderize.swap.token-fetcher.ts
@@ -0,0 +1,82 @@
import { Inject } from '@nestjs/common';

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 { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { ContractType } from '~position/contract.interface';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { TenderizeContractFactory } from '../contracts';
import { ethereumEndpoint } from '../helpers/constants';
import { ConfigResponse, configQuery } from '../helpers/queries';
import { TENDERIZE_DEFINITION } from '../tenderize.definition';

const appId = TENDERIZE_DEFINITION.id;
const groupId = TENDERIZE_DEFINITION.groups.swap.id;
const network = Network.ETHEREUM_MAINNET;

@Register.TokenPositionFetcher({ appId, groupId, network })
export class EthereumTenderizeSwapTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(TenderizeContractFactory) private readonly tenderizeContractFactory: TenderizeContractFactory,
) {}

async getPosition(address: string, steak: string, tenderAddress: string) {
const multicall = this.appToolkit.getMulticall(network);
const contract = this.tenderizeContractFactory.erc20({ address, network });
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const underlyingToken = baseTokens.find(v => v.address === steak)!;
const tenderTokens = await this.appToolkit.getAppTokenPositions({
appId: TENDERIZE_DEFINITION.id,
groupIds: [TENDERIZE_DEFINITION.groups.tender.id],
network: Network.ETHEREUM_MAINNET,
});
const tenderToken = tenderTokens.find(v => v.address === tenderAddress)!;

const [symbol, decimals, totalSupply] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
]);
const supply = Number(totalSupply) / 10 ** decimals;
const price = underlyingToken.price;
const token: AppTokenPosition = {
address,
network,
appId,
groupId,
symbol,
decimals,
supply,
tokens: [tenderToken, underlyingToken],
dataProps: {},
pricePerShare: [0.5, 0.5],
price,
type: ContractType.APP_TOKEN,
displayProps: {
label: `${getLabelFromToken(tenderToken)} / ${getLabelFromToken(underlyingToken)} SWAP`,
secondaryLabel: buildDollarDisplayItem(price),
images: [],
statsItems: [],
},
};

return token;
}

async getPositions() {
const data = await this.appToolkit.helpers.theGraphHelper.request<ConfigResponse>({
endpoint: ethereumEndpoint,
query: configQuery,
});

const positions = await Promise.all(
data.configs.map(async config => await this.getPosition(config.lpToken, config.steak, config.tenderToken)),
);
return positions;
}
}