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

Commit

Permalink
fix(camelot): Extract to Studio (#2707)
Browse files Browse the repository at this point in the history
  • Loading branch information
wpoulin authored May 29, 2023
1 parent a726e55 commit 242fb90
Show file tree
Hide file tree
Showing 28 changed files with 15,871 additions and 0 deletions.
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

0 comments on commit 242fb90

Please sign in to comment.