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

fix(camelot): Extract to Studio #2707

Merged
merged 1 commit into from
May 29, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Inject } from '@nestjs/common';
import { range } from 'lodash';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { DefaultDataProps } from '~position/display.interface';
import { MetaType } from '~position/position.interface';
import { isClaimable } from '~position/position.utils';
import { GetDefinitionsParams } from '~position/template/app-token.template.types';
import { ContractPositionTemplatePositionFetcher } from '~position/template/contract-position.template.position-fetcher';
import { GetTokenBalancesParams, GetTokenDefinitionsParams } from '~position/template/contract-position.template.types';

import { CamelotContractFactory, CamelotDividend } from '../contracts';

export type CamelotDividendDefinition = {
address: string;
suppliedTokenAddress: string;
rewardTokenAddresses: string[];
};

@PositionTemplate()
export class ArbitrumCamelotDividendContractPositionFetcher extends ContractPositionTemplatePositionFetcher<
CamelotDividend,
DefaultDataProps,
CamelotDividendDefinition
> {
groupLabel = 'Dividend';

dividendContractAddress = '0x5422aa06a38fd9875fc2501380b40659feebd3bb';

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

getContract(address: string): CamelotDividend {
return this.contractFactory.camelotDividend({ address, network: this.network });
}

async getDefinitions({ multicall }: GetDefinitionsParams): Promise<CamelotDividendDefinition[]> {
const dividenContract = this.contractFactory.camelotDividend({
address: this.dividendContractAddress,
network: this.network,
});

const numRewardToken = await multicall.wrap(dividenContract).distributedTokensLength();

const rewardTokenAddresses = await Promise.all(
range(0, numRewardToken.toNumber()).map(async index => {
const rewardTokenAddressRaw = await multicall.wrap(dividenContract).distributedToken(index);
return rewardTokenAddressRaw.toLowerCase();
}),
);

const suppliedTokenAddress = await multicall.wrap(dividenContract).xGrailToken();

return [{ address: this.dividendContractAddress, suppliedTokenAddress, rewardTokenAddresses }];
}

async getTokenDefinitions({ definition }: GetTokenDefinitionsParams<CamelotDividend, CamelotDividendDefinition>) {
return [
{
metaType: MetaType.SUPPLIED,
address: definition.suppliedTokenAddress,
network: this.network,
},
...definition.rewardTokenAddresses.map(address => ({
metaType: MetaType.CLAIMABLE,
address,
network: this.network,
})),
];
}

async getLabel() {
return `Dividends`;
}

async getTokenBalancesPerPosition({ address, contract, contractPosition }: GetTokenBalancesParams<CamelotDividend>) {
const allocation = await contract.usersAllocation(address);
const claimableTokens = contractPosition.tokens.filter(isClaimable);

const claimableBalances = await Promise.all(
claimableTokens.map(token => {
return contract.pendingDividendsAmount(token.address, address);
}),
);

return [allocation, ...claimableBalances];
}
}
144 changes: 144 additions & 0 deletions src/apps/camelot/arbitrum/camelot.farm.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Inject, NotImplementedException } from '@nestjs/common';
import { range } from 'lodash';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { drillBalance } from '~app-toolkit/helpers/drill-balance.helper';
import { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { DefaultDataProps } from '~position/display.interface';
import { ContractPositionBalance } from '~position/position-balance.interface';
import { MetaType } from '~position/position.interface';
import { GetDefinitionsParams } from '~position/template/app-token.template.types';
import { GetDisplayPropsParams, GetTokenDefinitionsParams } from '~position/template/contract-position.template.types';
import { CustomContractPositionTemplatePositionFetcher } from '~position/template/custom-contract-position.template.position-fetcher';

import { CamelotContractFactory, CamelotNftPool } from '../contracts';

type CamelotFarmContractPositionDefinition = {
address: string;
stakingTokenAddress: string;
rewardTokenAddresses: string[];
};

@PositionTemplate()
export class ArbitrumCamelotFarmContractPositionFetcher extends CustomContractPositionTemplatePositionFetcher<CamelotNftPool> {
groupLabel = 'Farm';

masterContractAddress = '0x55401a4f396b3655f66bf6948a1a4dc61dfc21f4';

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

async getDefinitions({ multicall }: GetDefinitionsParams): Promise<CamelotFarmContractPositionDefinition[]> {
const masterContract = this.contractFactory.camelotMaster({
address: this.masterContractAddress,
network: this.network,
});
const poolLength = await multicall.wrap(masterContract).poolsLength();

const poolAddresses = await Promise.all(
range(0, Number(poolLength)).map(async i => {
const poolAddressRaw = await multicall.wrap(masterContract).getPoolAddressByIndex(i);
return poolAddressRaw.toLowerCase();
}),
);

const farmDefinitions = await Promise.all(
poolAddresses.map(async address => {
const nftPoolContract = this.contractFactory.camelotNftPool({ address, network: this.network });
const poolInfo = await multicall.wrap(nftPoolContract).getPoolInfo();
const { lpToken, grailToken, xGrailToken } = poolInfo;

return {
address,
stakingTokenAddress: lpToken.toLowerCase(),
rewardTokenAddresses: [grailToken.toLowerCase(), xGrailToken.toLowerCase()],
};
}),
);

return farmDefinitions;
}

async getTokenDefinitions({
definition,
}: GetTokenDefinitionsParams<CamelotNftPool, CamelotFarmContractPositionDefinition>) {
return [
{
metaType: MetaType.SUPPLIED,
address: definition.stakingTokenAddress,
network: this.network,
},
...definition.rewardTokenAddresses.map(address => ({
metaType: MetaType.CLAIMABLE,
address,
network: this.network,
})),
];
}

getContract(address: string): CamelotNftPool {
return this.contractFactory.camelotNftPool({ network: this.network, address });
}

async getLabel({ contractPosition }: GetDisplayPropsParams<CamelotNftPool>) {
return `${getLabelFromToken(contractPosition.tokens[0])}`;
}

getTokenBalancesPerPosition(): never {
throw new NotImplementedException();
}

async getBalances(address: string): Promise<ContractPositionBalance<DefaultDataProps>[]> {
const multicall = this.appToolkit.getMulticall(this.network);

const contractPositions = await this.appToolkit.getAppContractPositions({
appId: this.appId,
network: this.network,
groupIds: [this.groupId],
});

const balances = await Promise.all(
contractPositions.map(async contractPosition => {
const nftPoolContract = this.contractFactory.camelotNftPool({
address: contractPosition.address,
network: this.network,
});
const [numPositionsRaw, xGrailRewardsShareRaw] = await Promise.all([
multicall.wrap(nftPoolContract).balanceOf(address),
multicall.wrap(nftPoolContract).xGrailRewardsShare(),
]);

return await Promise.all(
range(0, Number(numPositionsRaw)).map(async i => {
const tokenId = await multicall.wrap(nftPoolContract).tokenOfOwnerByIndex(address, i);
const [stakingPosition, rewardAmountsCombined] = await Promise.all([
multicall.wrap(nftPoolContract).getStakingPosition(tokenId),
multicall.wrap(nftPoolContract).pendingRewards(tokenId),
]);
const xGrailRewardsShare = Number(xGrailRewardsShareRaw) / 10 ** 4;

const xGrailBalance = Number(rewardAmountsCombined) * xGrailRewardsShare;
const grailBalance = Number(rewardAmountsCombined) - xGrailBalance;

const suppliedtAmount = drillBalance(contractPosition.tokens[0], stakingPosition.amount.toString());
const grailAmount = drillBalance(contractPosition.tokens[1], grailBalance.toString());
const xGrailAmount = drillBalance(contractPosition.tokens[2], xGrailBalance.toString());

return {
...contractPosition,
tokens: [suppliedtAmount, grailAmount, xGrailAmount],
balanceUSD: suppliedtAmount.balanceUSD + grailAmount.balanceUSD + xGrailAmount.balanceUSD,
};
}),
);
}),
);

return balances.flat();
}
}
10 changes: 10 additions & 0 deletions src/apps/camelot/arbitrum/camelot.pool.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { UniswapV2DefaultPoolSubgraphTemplateTokenFetcher } from '~apps/uniswap-v2/common/uniswap-v2.default.subgraph.template.token-fetcher';

@PositionTemplate()
export class ArbitrumCamelotPoolTokenFetcher extends UniswapV2DefaultPoolSubgraphTemplateTokenFetcher {
groupLabel = 'Pools';

subgraphUrl = 'https://api.thegraph.com/subgraphs/name/camelotlabs/camelot-amm';
factoryAddress = '0x6eccab422d763ac031210895c81787e87b43a652';
}
36 changes: 36 additions & 0 deletions src/apps/camelot/arbitrum/camelot.x-grail.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher';
import { GetUnderlyingTokensParams } from '~position/template/app-token.template.types';

import { CamelotContractFactory, CamelotXGrail } from '../contracts';

@PositionTemplate()
export class ArbitrumXGrailTokenFetcher extends AppTokenTemplatePositionFetcher<CamelotXGrail> {
groupLabel = 'xGRAIL';

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

getContract(address: string): CamelotXGrail {
return this.contractFactory.camelotXGrail({ address, network: this.network });
}

async getAddresses(): Promise<string[]> {
return ['0x3caae25ee616f2c8e13c74da0813402eae3f496b'];
}

async getUnderlyingTokenDefinitions({ contract }: GetUnderlyingTokensParams<CamelotXGrail>) {
return [{ address: await contract.grailToken(), network: this.network }];
}

async getPricePerShare() {
return [1];
}
}
Binary file added src/apps/camelot/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/apps/camelot/camelot.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';

import { AbstractApp } from '~app/app.dynamic-module';
import { UniswapV2ContractFactory } from '~apps/uniswap-v2/contracts';

import { ArbitrumCamelotDividendContractPositionFetcher } from './arbitrum/camelot.dividend.contract-position-fetcher';
import { ArbitrumCamelotFarmContractPositionFetcher } from './arbitrum/camelot.farm.contract-position-fetcher';
import { ArbitrumCamelotPoolTokenFetcher } from './arbitrum/camelot.pool.token-fetcher';
import { ArbitrumXGrailTokenFetcher } from './arbitrum/camelot.x-grail.token-fetcher';
import { CamelotContractFactory } from './contracts';

@Module({
providers: [
CamelotContractFactory,
UniswapV2ContractFactory,
// Arbitrum
ArbitrumCamelotPoolTokenFetcher,
ArbitrumXGrailTokenFetcher,
ArbitrumCamelotDividendContractPositionFetcher,
ArbitrumCamelotFarmContractPositionFetcher,
],
})
export class CamelotAppModule extends AbstractApp() {}
Loading