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

Commit

Permalink
feat(velodrome): Move to templates (#1310)
Browse files Browse the repository at this point in the history
  • Loading branch information
immasandwich authored Aug 31, 2022
1 parent 6ea68eb commit aabbda6
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 245 deletions.
94 changes: 0 additions & 94 deletions src/apps/velodrome/optimism/velodrome.balance-fetcher.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Inject } from '@nestjs/common';
import axios from 'axios';
import { range } from 'lodash';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { isClaimable } from '~position/position.utils';
import {
GetTokenDefinitionsParams,
GetDataPropsParams,
GetTokenBalancesParams,
} from '~position/template/contract-position.template.types';
import {
SingleStakingFarmDataProps,
SingleStakingFarmDynamicTemplateContractPositionFetcher,
} from '~position/template/single-staking.dynamic.template.contract-position-fetcher';
import { Network } from '~types/network.interface';

import { VelodromeContractFactory, VelodromeGauge } from '../contracts';
import { VELODROME_DEFINITION } from '../velodrome.definition';

import { VelodromeApiPairData } from './velodrome.pool.token-fetcher';

const appId = VELODROME_DEFINITION.id;
const groupId = VELODROME_DEFINITION.groups.farm.id;
const network = Network.OPTIMISM_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class OptimismVelodromeStakingContractPositionFetcher extends SingleStakingFarmDynamicTemplateContractPositionFetcher<VelodromeGauge> {
appId = appId;
groupId = groupId;
network = network;
groupLabel = 'Staking';

constructor(
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
@Inject(VelodromeContractFactory) protected readonly contractFactory: VelodromeContractFactory,
) {
super(appToolkit);
}

getContract(address: string): VelodromeGauge {
return this.contractFactory.velodromeGauge({ address, network: this.network });
}

async getFarmAddresses() {
const { data } = await axios.get<{ data: VelodromeApiPairData[] }>('https://api.velodrome.finance/api/v1/pairs');
const gaugeAddresses = data.data.map(pool => pool.gauge_address).filter(v => !!v);
return gaugeAddresses;
}

async getStakedTokenAddress({ contract }: GetTokenDefinitionsParams<VelodromeGauge>) {
return contract.stake();
}

async getRewardTokenAddresses({ contract }: GetTokenDefinitionsParams<VelodromeGauge>) {
const numRewards = Number(await contract.rewardsListLength());
return Promise.all(range(numRewards).map(async n => await contract.rewards(n)));
}

getRewardRates({ contract, contractPosition }: GetDataPropsParams<VelodromeGauge, SingleStakingFarmDataProps>) {
const rewardTokens = contractPosition.tokens.filter(isClaimable);
return Promise.all(rewardTokens.map(rt => contract.rewardPerToken(rt.address)));
}

async getStakedTokenBalance({
address,
contract,
}: GetTokenBalancesParams<VelodromeGauge, SingleStakingFarmDataProps>) {
return contract.balanceOf(address);
}

getRewardTokenBalances({
address,
contract,
contractPosition,
}: GetTokenBalancesParams<VelodromeGauge, SingleStakingFarmDataProps>) {
const rewardTokens = contractPosition.tokens.filter(isClaimable);
return Promise.all(rewardTokens.map(rt => contract.earned(rt.address, address)));
}
}
118 changes: 74 additions & 44 deletions src/apps/velodrome/optimism/velodrome.pool.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Inject } from '@nestjs/common';
import Axios from 'axios';
import { BigNumber } from 'ethers';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { CurvePoolTokenHelper } from '~apps/curve';
import { CacheOnInterval } from '~cache/cache-on-interval.decorator';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present';
import { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { DefaultDataProps } from '~position/display.interface';
import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher';
import {
GetDataPropsParams,
GetDisplayPropsParams,
GetPricePerShareParams,
GetUnderlyingTokensParams,
} from '~position/template/app-token.template.types';
import { Network } from '~types/network.interface';

import { VelodromeContractFactory, VelodromePool } from '../contracts';
Expand All @@ -16,56 +22,80 @@ const appId = VELODROME_DEFINITION.id;
const groupId = VELODROME_DEFINITION.groups.pool.id;
const network = Network.OPTIMISM_MAINNET;

interface PairData {
export interface VelodromeApiPairData {
address: string;
gauge_address: string;
token0_address: string;
token1_address: string;
apr: number;
}

export type VelodromePoolTokenDataProps = {
liquidity: number;
reserves: number[];
};

@Register.TokenPositionFetcher({ appId, groupId, network })
export class OptimismVelodromePoolsTokenFetcher implements PositionFetcher<AppTokenPosition> {
export class OptimismVelodromePoolsTokenFetcher extends AppTokenTemplatePositionFetcher<
VelodromePool,
VelodromePoolTokenDataProps
> {
appId = appId;
groupId = groupId;
network = network;
groupLabel = 'Pools';

constructor(
@Inject(CurvePoolTokenHelper)
private readonly curvePoolTokenHelper: CurvePoolTokenHelper,
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
@Inject(VelodromeContractFactory) private readonly contractFactory: VelodromeContractFactory,
) {}

@CacheOnInterval({
key: `studio:${network}:${appId}:${groupId}:definitions`,
timeout: 15 * 60 * 1000,
})
async getDefinitions() {
const { data } = await Axios.get<{ data: PairData[] }>('https://api.velodrome.finance/api/v1/pairs');
return data;
) {
super(appToolkit);
}

async getPositions() {
const { data } = await this.getDefinitions();

const poolDefinitions = data.map(pool => {
return {
swapAddress: pool.address.toLowerCase(),
tokenAddress: pool.address.toLowerCase(),
gaugeAddresses: [pool.gauge_address.toLowerCase()],
apy: pool.apr,
};
});

return this.curvePoolTokenHelper.getTokens<VelodromePool>({
network,
appId,
groupId,
poolDefinitions,
resolvePoolContract: ({ network, address }) => this.contractFactory.velodromePool({ network, address }),
resolvePoolCoinAddresses: async ({ multicall, poolContract }) =>
Promise.all([multicall.wrap(poolContract).token0(), multicall.wrap(poolContract).token1()]),
resolvePoolReserves: async ({ multicall, poolContract }) =>
Promise.all([multicall.wrap(poolContract).reserve0(), multicall.wrap(poolContract).reserve1()]),
resolvePoolFee: async () => BigNumber.from(0), // TODO: get actual value
resolvePoolTokenPrice: async ({ tokens, reserves, supply }) =>
(tokens[0].price * reserves[0] + tokens[1].price * reserves[1]) / supply,
});
getContract(address: string): VelodromePool {
return this.contractFactory.velodromePool({ address, network: this.network });
}

async getAddresses() {
const { data } = await Axios.get<{ data: VelodromeApiPairData[] }>('https://api.velodrome.finance/api/v1/pairs');
return data.data.map(pool => pool.address);
}

async getUnderlyingTokenAddresses({ contract }: GetUnderlyingTokensParams<VelodromePool>) {
return Promise.all([contract.token0(), contract.token1()]);
}

async getPricePerShare({ appToken, contract }: GetPricePerShareParams<VelodromePool, DefaultDataProps>) {
const [token0, token1] = appToken.tokens;
const [reserve0, reserve1] = await Promise.all([contract.reserve0(), contract.reserve1()]);
const reserves = [Number(reserve0) / 10 ** token0.decimals, Number(reserve1) / 10 ** token1.decimals];
const pricePerShare = reserves.map(r => r / appToken.supply);
return pricePerShare;
}

async getDataProps({ appToken }: GetDataPropsParams<VelodromePool, VelodromePoolTokenDataProps>) {
const reserves = (appToken.pricePerShare as number[]).map(v => v * appToken.supply);
const liquidity = appToken.price * appToken.supply;
return { liquidity, reserves };
}

async getLabel({ appToken }: GetDisplayPropsParams<VelodromePool, VelodromePoolTokenDataProps>): Promise<string> {
return appToken.tokens.map(v => getLabelFromToken(v)).join(' / ');
}

async getSecondaryLabel({ appToken }: GetDisplayPropsParams<VelodromePool, VelodromePoolTokenDataProps>) {
const { liquidity, reserves } = appToken.dataProps;
const reservePercentages = appToken.tokens.map((t, i) => reserves[i] * (t.price / liquidity));
return reservePercentages.map(p => `${Math.round(p * 100)}%`).join(' / ');
}

async getStatsItems({ appToken }: GetDisplayPropsParams<VelodromePool, VelodromePoolTokenDataProps>) {
const { reserves, liquidity } = appToken.dataProps;
const reservesDisplay = reserves.map(v => (v < 0.01 ? '<0.01' : v.toFixed(2))).join(' / ');

return [
{ label: 'Liquidity', value: buildDollarDisplayItem(liquidity) },
{ label: 'Reserves', value: reservesDisplay },
];
}
}

This file was deleted.

Loading

0 comments on commit aabbda6

Please sign in to comment.