Skip to content

Commit

Permalink
feat(tenderize): Integrate Tender tokens and Swap tokens (Zapper-fi#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmaark authored and volt62 committed Aug 2, 2022
1 parent 6ce5a89 commit bb3a5b2
Show file tree
Hide file tree
Showing 13 changed files with 596 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/apps/tenderize/arbitrum/tenderize.balance-fetcher.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit bb3a5b2

Please sign in to comment.