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

Commit

Permalink
feat: Migrate Curve over (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
JForsaken authored Apr 22, 2022
1 parent ec71d80 commit 1043bc8
Show file tree
Hide file tree
Showing 134 changed files with 49,912 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"eslint-plugin-unused-imports": "^2.0.0",
"ethers": "^5.5.1",
"graphql": "^15.5.1",
"graphql-request": "^3.4.0",
"graphql-request": "^3.7.0",
"jest": "^27.5.1",
"moment": "^2.29.2",
"nodemon": "^2.0.15",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type SingleStakingFarmContractPositionHelperParams<T> = {
network: Network;
}) => SingleStakingFarmDefinition[] | Promise<SingleStakingFarmDefinition[]>;
resolveImplementation?: () => string;
resolveFarmAddresses?: (opts: { network: Network }) => string[] | Promise<string[]>;
resolveFarmAddresses?: (opts: { network: Network }) => (string | null)[] | Promise<(string | null)[]>;
resolveStakedTokenAddress?: (opts: { contract: T; multicall: Multicall; index: number }) => Promise<string>;
resolveRewardTokenAddresses?: (opts: { contract: T; multicall: Multicall }) => Promise<string | string[]>;
resolveTotalValueLocked?: SingleStakingFarmResolveTotalValueLockedParams<T>;
Expand Down Expand Up @@ -117,7 +117,9 @@ export class SingleStakingFarmContractPositionHelper {

const contractPositions = await Promise.all(
farmDefinitionsOrAddresses.map(
async (definitionOrAddress: string | SingleStakingFarmDefinition, index: number) => {
async (definitionOrAddress: string | SingleStakingFarmDefinition | null, index: number) => {
if (!definitionOrAddress) return null;

const type = ContractType.POSITION;
const address = typeof definitionOrAddress === 'string' ? definitionOrAddress : definitionOrAddress.address;
const contract = resolveFarmContract({ address, network });
Expand Down
64 changes: 64 additions & 0 deletions src/apps/curve/arbitrum/curve.balance-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Inject } from '@nestjs/common';

import { SingleStakingContractPositionBalanceHelper, 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 { isClaimable } from '~position/position.utils';
import { Network } from '~types/network.interface';

import { CurveContractFactory, CurveGaugeV2 } from '../contracts';
import { CURVE_DEFINITION } from '../curve.definition';

@Register.BalanceFetcher(CURVE_DEFINITION.id, Network.ARBITRUM_MAINNET)
export class ArbitrumCurveBalanceFetcher implements BalanceFetcher {
constructor(
@Inject(TokenBalanceHelper) private readonly tokenBalanceHelper: TokenBalanceHelper,
@Inject(CurveContractFactory) private readonly curveContractFactory: CurveContractFactory,
@Inject(SingleStakingContractPositionBalanceHelper)
private readonly singleStakingContractPositionBalanceHelper: SingleStakingContractPositionBalanceHelper,
) {}

private async getPoolTokenBalances(address: string) {
return this.tokenBalanceHelper.getTokenBalances({
network: Network.ARBITRUM_MAINNET,
appId: CURVE_DEFINITION.id,
groupId: CURVE_DEFINITION.groups.pool.id,
address,
});
}

private async getStakedBalances(address: string) {
return this.singleStakingContractPositionBalanceHelper.getBalances<CurveGaugeV2>({
address,
appId: CURVE_DEFINITION.id,
groupId: CURVE_DEFINITION.groups.farm.id,
network: Network.ARBITRUM_MAINNET,
resolveContract: ({ address, network }) => this.curveContractFactory.curveGaugeV2({ address, network }),
resolveStakedTokenBalance: ({ contract, address, multicall }) => multicall.wrap(contract).balanceOf(address),
resolveRewardTokenBalances: ({ contract, address, multicall, contractPosition }) => {
const rewardTokens = contractPosition.tokens.filter(isClaimable);
const wrappedContract = multicall.wrap(contract);
return Promise.all(rewardTokens.map(v => wrappedContract.claimable_reward_write(address, v.address)));
},
});
}

async getBalances(address: string) {
const [poolTokenBalances, stakedBalances] = await Promise.all([
this.getPoolTokenBalances(address),
this.getStakedBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'Pools',
assets: poolTokenBalances,
},
{
label: 'Staked',
assets: stakedBalances,
},
]);
}
}
50 changes: 50 additions & 0 deletions src/apps/curve/arbitrum/curve.farm.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Inject } from '@nestjs/common';

import { SingleStakingFarmContractPositionHelper } from '~app-toolkit';
import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { CurveContractFactory, CurveGaugeV2 } from '../contracts';
import { CURVE_DEFINITION } from '../curve.definition';
import { CurveGaugeV2RewardTokenStrategy } from '../helpers/curve.gauge-v2.reward-token-strategy';
import { CurveGaugeV2RoiStrategy } from '../helpers/curve.gauge-v2.roi-strategy';

import { CURVE_V1_POOL_DEFINITIONS, CURVE_V2_POOL_DEFINITIONS } from './curve.pool.definitions';

const appId = CURVE_DEFINITION.id;
const groupId = CURVE_DEFINITION.groups.farm.id;
const network = Network.ARBITRUM_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class ArbitrumCurveFarmContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(SingleStakingFarmContractPositionHelper)
private readonly singleStakingFarmContractPositionHelper: SingleStakingFarmContractPositionHelper,
@Inject(CurveContractFactory)
private readonly curveContractFactory: CurveContractFactory,
@Inject(CurveGaugeV2RoiStrategy)
private readonly curveGaugeV2RoiStrategy: CurveGaugeV2RoiStrategy,
@Inject(CurveGaugeV2RewardTokenStrategy)
private readonly curveGaugeV2RewardTokenStrategy: CurveGaugeV2RewardTokenStrategy,
) {}

async getPositions() {
const definitions = [CURVE_V1_POOL_DEFINITIONS, CURVE_V2_POOL_DEFINITIONS].flat().filter(v => !!v.gaugeAddress);
return this.singleStakingFarmContractPositionHelper.getContractPositions<CurveGaugeV2>({
network,
appId,
groupId,
dependencies: [{ appId: CURVE_DEFINITION.id, groupIds: [CURVE_DEFINITION.groups.pool.id], network }],
resolveFarmAddresses: () => definitions.map(v => v.gaugeAddress ?? null),
resolveFarmContract: ({ address, network }) => this.curveContractFactory.curveGaugeV2({ address, network }),
resolveStakedTokenAddress: ({ contract, multicall }) => multicall.wrap(contract).lp_token(),
resolveRewardTokenAddresses: this.curveGaugeV2RewardTokenStrategy.build(),
resolveIsActive: () => true,
resolveRois: this.curveGaugeV2RoiStrategy.build({
tokenDefinitions: definitions,
}),
});
}
}
31 changes: 31 additions & 0 deletions src/apps/curve/arbitrum/curve.pool.definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CurvePoolDefinition } from '../curve.types';

export const CURVE_V1_POOL_DEFINITIONS: CurvePoolDefinition[] = [
{
queryKey: '2pool',
label: '2Pool Curve',
swapAddress: '0x7f90122bf0700f9e7e1f688fe926940e8839f353',
tokenAddress: '0x7f90122bf0700f9e7e1f688fe926940e8839f353',
gaugeAddress: '0xbf7e49483881c76487b0989cd7d9a8239b20ca41',
streamAddress: '0xff17560d746f85674fe7629ce986e949602ef948',
},
{
queryKey: 'renBTC',
label: 'renBTC Curve',
swapAddress: '0x3e01dd8a5e1fb3481f0f589056b428fc308af0fb',
tokenAddress: '0x3e01dd8a5e1fb3481f0f589056b428fc308af0fb',
gaugeAddress: '0xc2b1df84112619d190193e48148000e3990bf627',
streamAddress: '0x9f86c5142369b1ffd4223e5a2f2005fc66807894',
},
];

export const CURVE_V2_POOL_DEFINITIONS: CurvePoolDefinition[] = [
{
queryKey: 'tricrypto',
label: 'TriCrypto Curve',
swapAddress: '0x960ea3e3c7fb317332d990873d354e18d7645590',
tokenAddress: '0x8e0b8c8bb9db49a46697f3a5bb8a308e744821d2',
gaugeAddress: '0x97e2768e8e73511ca874545dc5ff8067eb19b787',
streamAddress: '0x9044e12fb1732f88ed0c93cfa5e9bb9bd2990ce5',
},
];
62 changes: 62 additions & 0 deletions src/apps/curve/arbitrum/curve.pool.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Inject } from '@nestjs/common';

import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { CURVE_DEFINITION } from '../curve.definition';
import { CurveFactoryPoolTokenHelper } from '../helpers/curve.factory-pool.token-helper';
import { CurveV1PoolTokenHelper } from '../helpers/curve.v1-pool.token-helper';
import { CurveV2PoolTokenHelper } from '../helpers/curve.v2-pool.token-helper';

import { CURVE_V1_POOL_DEFINITIONS, CURVE_V2_POOL_DEFINITIONS } from './curve.pool.definitions';

const appId = CURVE_DEFINITION.id;
const groupId = CURVE_DEFINITION.groups.pool.id;
const network = Network.ARBITRUM_MAINNET;

@Register.TokenPositionFetcher({ appId, groupId, network })
export class ArbitrumCurvePoolTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(CurveV1PoolTokenHelper)
private readonly curveV1PoolTokenHelper: CurveV1PoolTokenHelper,
@Inject(CurveV2PoolTokenHelper)
private readonly curveV2PoolTokenHelper: CurveV2PoolTokenHelper,
@Inject(CurveFactoryPoolTokenHelper)
private readonly curveFactoryPoolTokenHelper: CurveFactoryPoolTokenHelper,
) {}

async getPositions() {
const [v1Pools] = await Promise.all([
this.curveV1PoolTokenHelper.getTokens({
network,
appId,
groupId,
poolDefinitions: CURVE_V1_POOL_DEFINITIONS,
statsUrl: 'https://stats.curve.fi/raw-stats-arbitrum/apys.json',
}),
]);

const [v2Pools, v2FactoryPools] = await Promise.all([
this.curveV2PoolTokenHelper.getTokens({
network,
appId,
groupId,
poolDefinitions: CURVE_V2_POOL_DEFINITIONS,
statsUrl: 'https://stats.curve.fi/raw-stats-arbitrum/apys.json',
baseCurveTokens: v1Pools,
}),
this.curveFactoryPoolTokenHelper.getTokens({
factoryAddress: '0xb17b674d9c5cb2e441f8e196a2f048a81355d031',
network,
appId,
groupId,
baseCurveTokens: v1Pools,
skipVolume: true, // Arbitrum public RPC can't handle this
}),
]);

return [v1Pools, v2Pools, v2FactoryPools].flat();
}
}
64 changes: 64 additions & 0 deletions src/apps/curve/avalanche/curve.balance-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Inject } from '@nestjs/common';

import { SingleStakingContractPositionBalanceHelper, 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 { isClaimable } from '~position/position.utils';
import { Network } from '~types/network.interface';

import { CurveContractFactory, CurveGaugeV2 } from '../contracts';
import { CURVE_DEFINITION } from '../curve.definition';

@Register.BalanceFetcher(CURVE_DEFINITION.id, Network.AVALANCHE_MAINNET)
export class AvalancheCurveBalanceFetcher implements BalanceFetcher {
constructor(
@Inject(TokenBalanceHelper) private readonly tokenBalanceHelper: TokenBalanceHelper,
@Inject(CurveContractFactory) private readonly curveContractFactory: CurveContractFactory,
@Inject(SingleStakingContractPositionBalanceHelper)
private readonly singleStakingContractPositionBalanceHelper: SingleStakingContractPositionBalanceHelper,
) {}

private async getPoolTokenBalances(address: string) {
return this.tokenBalanceHelper.getTokenBalances({
network: Network.AVALANCHE_MAINNET,
appId: CURVE_DEFINITION.id,
groupId: CURVE_DEFINITION.groups.pool.id,
address,
});
}

private async getStakedBalances(address: string) {
return this.singleStakingContractPositionBalanceHelper.getBalances<CurveGaugeV2>({
address,
appId: CURVE_DEFINITION.id,
groupId: CURVE_DEFINITION.groups.farm.id,
network: Network.AVALANCHE_MAINNET,
resolveContract: ({ address, network }) => this.curveContractFactory.curveGaugeV2({ address, network }),
resolveStakedTokenBalance: ({ contract, address, multicall }) => multicall.wrap(contract).balanceOf(address),
resolveRewardTokenBalances: ({ contract, address, multicall, contractPosition }) => {
const rewardTokens = contractPosition.tokens.filter(isClaimable);
const wrappedContract = multicall.wrap(contract);
return Promise.all(rewardTokens.map(v => wrappedContract.claimable_reward_write(address, v.address)));
},
});
}

async getBalances(address: string) {
const [poolTokenBalances, stakedBalances] = await Promise.all([
this.getPoolTokenBalances(address),
this.getStakedBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'Pools',
assets: poolTokenBalances,
},
{
label: 'Staked',
assets: stakedBalances,
},
]);
}
}
50 changes: 50 additions & 0 deletions src/apps/curve/avalanche/curve.farm.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Inject } from '@nestjs/common';

import { SingleStakingFarmContractPositionHelper } from '~app-toolkit';
import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { CurveContractFactory, CurveGaugeV2 } from '../contracts';
import { CURVE_DEFINITION } from '../curve.definition';
import { CurveGaugeV2RewardTokenStrategy } from '../helpers/curve.gauge-v2.reward-token-strategy';
import { CurveGaugeV2RoiStrategy } from '../helpers/curve.gauge-v2.roi-strategy';

import { CURVE_V1_POOL_DEFINITIONS, CURVE_V2_POOL_DEFINITIONS } from './curve.pool.definitions';

const appId = CURVE_DEFINITION.id;
const groupId = CURVE_DEFINITION.groups.farm.id;
const network = Network.AVALANCHE_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class AvalancheCurveFarmContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(SingleStakingFarmContractPositionHelper)
private readonly singleStakingFarmContractPositionHelper: SingleStakingFarmContractPositionHelper,
@Inject(CurveContractFactory)
private readonly curveContractFactory: CurveContractFactory,
@Inject(CurveGaugeV2RoiStrategy)
private readonly curveGaugeV2RoiStrategy: CurveGaugeV2RoiStrategy,
@Inject(CurveGaugeV2RewardTokenStrategy)
private readonly curveGaugeV2RewardTokenStrategy: CurveGaugeV2RewardTokenStrategy,
) {}

async getPositions() {
const definitions = [CURVE_V1_POOL_DEFINITIONS, CURVE_V2_POOL_DEFINITIONS].flat().filter(v => !!v.gaugeAddress);
return this.singleStakingFarmContractPositionHelper.getContractPositions<CurveGaugeV2>({
network,
appId,
groupId,
dependencies: [{ appId: CURVE_DEFINITION.id, groupIds: [CURVE_DEFINITION.groups.pool.id], network }],
resolveFarmAddresses: () => definitions.map(v => v.gaugeAddress ?? null),
resolveFarmContract: ({ address, network }) => this.curveContractFactory.curveGaugeV2({ address, network }),
resolveStakedTokenAddress: ({ contract, multicall }) => multicall.wrap(contract).lp_token(),
resolveRewardTokenAddresses: this.curveGaugeV2RewardTokenStrategy.build(),
resolveIsActive: () => true,
resolveRois: this.curveGaugeV2RoiStrategy.build({
tokenDefinitions: definitions,
}),
});
}
}
31 changes: 31 additions & 0 deletions src/apps/curve/avalanche/curve.pool.definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CurvePoolDefinition } from '../curve.types';

export const CURVE_V1_POOL_DEFINITIONS: CurvePoolDefinition[] = [
{
queryKey: 'aave',
label: 'Aave 3Pool Curve',
swapAddress: '0x7f90122bf0700f9e7e1f688fe926940e8839f353',
tokenAddress: '0x1337bedc9d22ecbe766df105c9623922a27963ec',
gaugeAddress: '0x5b5cfe992adac0c9d48e05854b2d91c73a003858',
streamAddress: '0xb504b6eb06760019801a91b451d3f7bd9f027fc9',
},
{
queryKey: 'renBTC',
label: 'renBTC Curve',
swapAddress: '0x16a7da911a4dd1d83f3ff066fe28f3c792c50d90',
tokenAddress: '0xc2b1df84112619d190193e48148000e3990bf627',
gaugeAddress: '0x0f9cb53ebe405d49a0bbdbd291a65ff571bc83e1',
streamAddress: '0x75d05190f35567e79012c2f0a02330d3ed8a1f74',
},
];

export const CURVE_V2_POOL_DEFINITIONS: CurvePoolDefinition[] = [
{
queryKey: 'atricrypto',
label: 'Aave TriCrypto Curve',
swapAddress: '0xb755b949c126c04e0348dd881a5cf55d424742b2',
tokenAddress: '0x1dab6560494b04473a0be3e7d83cf3fdf3a51828',
gaugeAddress: '0x445fe580ef8d70ff569ab36e80c647af338db351',
streamAddress: '0xa05e565ca0a103fcd999c7a7b8de7bd15d5f6505',
},
];
Loading

0 comments on commit 1043bc8

Please sign in to comment.