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

Commit

Permalink
feat(homora-v2): Add Homora V2 integration (#1596)
Browse files Browse the repository at this point in the history
  • Loading branch information
mintcnn committed Oct 20, 2022
1 parent ec90500 commit 7fbd35e
Show file tree
Hide file tree
Showing 28 changed files with 21,168 additions and 0 deletions.
@@ -0,0 +1,15 @@
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { Network } from '~types/network.interface';

import { HomoraV2FarmContractPositionFetcher } from '../common/homora-v2.farm.contract-position-fetcher';
import { HOMORA_V_2_DEFINITION } from '../homora-v2.definition';

@PositionTemplate()
export class AvalancheHomoraV2FarmContractPositionFetcher extends HomoraV2FarmContractPositionFetcher {
appId = HOMORA_V_2_DEFINITION.id;
groupId = HOMORA_V_2_DEFINITION.groups.farm.id;
network = Network.AVALANCHE_MAINNET;
homoraBankAddress = '0x376d16c7de138b01455a51da79ad65806e9cd694';

groupLabel = 'Farms';
}
13 changes: 13 additions & 0 deletions src/apps/homora-v2/avalanche/homora-v2.lending.token-fetcher.ts
@@ -0,0 +1,13 @@
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';
import { Network } from '~types/network.interface';

import { HomoraV2LendingTokenFetcher } from '../common/homora-v2.lending.token-position-fetcher';
import { HOMORA_V_2_DEFINITION } from '../homora-v2.definition';

@PositionTemplate()
export class AvalancheHomoraV2LendingTokenFetcher extends HomoraV2LendingTokenFetcher {
appId = HOMORA_V_2_DEFINITION.id;
groupId = HOMORA_V_2_DEFINITION.groups.lending.id;
network = Network.AVALANCHE_MAINNET;
groupLabel = 'Lending';
}
124 changes: 124 additions & 0 deletions src/apps/homora-v2/common/homora-v2.farm.contract-position-fetcher.ts
@@ -0,0 +1,124 @@
import { Inject, NotImplementedException } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { ZERO_ADDRESS } from '~app-toolkit/constants/address';
import { ContractPosition, MetaType } from '~position/position.interface';
import { ContractPositionTemplatePositionFetcher } from '~position/template/contract-position.template.position-fetcher';
import {
GetDataPropsParams,
GetDisplayPropsParams,
GetTokenDefinitionsParams,
} from '~position/template/contract-position.template.types';
import { NETWORK_IDS } from '~types';

import { HomoraBank, HomoraV2ContractFactory } from '../contracts';
import httpClient from '../helpers/httpClient';
import { Exchange, Poolstatus } from '../interfaces/enums';
import { HomoraV2FarmingPositionDataProps, HomoraV2FarmingPositionDefinition, Pool } from '../interfaces/interfaces';

export abstract class HomoraV2FarmContractPositionFetcher extends ContractPositionTemplatePositionFetcher<
HomoraBank,
HomoraV2FarmingPositionDataProps,
HomoraV2FarmingPositionDefinition
> {
abstract homoraBankAddress: string;

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

getContract(address: string): HomoraBank {
return this.contractFactory.homoraBank({ address, network: this.network });
}

async getDefinitions(): Promise<HomoraV2FarmingPositionDefinition[]> {
const pools = await this.getPools();
const tradingVolumes = await this.getTradingVolumns();

return pools
.filter(
pool =>
![Poolstatus.delisting, Poolstatus.delisted, Poolstatus.emergency_delisted].includes(pool.status!) &&
!pool.migrateTo,
)
.map(pool => ({
address: this.homoraBankAddress,
exchange: pool.exchange.name,
poolAddress: pool.lpTokenAddress.toLowerCase(),
poolName: pool.name,
tokenAddresses: pool.tokens.map((token: string) => token.toLowerCase()),
feeTier:
pool.exchange.name === Exchange.UniswapV3 ? Number(pool.uniswapV3Info?.poolFeeBps) / 10 ** 4 : undefined,
rewardAddress: pool.rewardAddress,
tradingVolume: Number(tradingVolumes[pool.key]),
}));
}

async getTokenDefinitions({ definition }: GetTokenDefinitionsParams<HomoraBank, HomoraV2FarmingPositionDefinition>) {
return definition.tokenAddresses.map((token: string) => ({ metaType: MetaType.SUPPLIED, address: token }));
}

// second label -> pool weight

async getDataProps({
definition,
}: GetDataPropsParams<
HomoraBank,
HomoraV2FarmingPositionDataProps,
HomoraV2FarmingPositionDefinition
>): Promise<HomoraV2FarmingPositionDataProps> {
const { poolAddress, feeTier } = definition;

return {
poolAddress,
tradingVolume: definition.tradingVolume,
feeTier,
};
}

async getLabel({
definition,
}: GetDisplayPropsParams<
HomoraBank,
HomoraV2FarmingPositionDataProps,
HomoraV2FarmingPositionDefinition
>): Promise<string> {
if (definition.feeTier) {
const label = `[${definition.exchange}] ${definition.poolName} (${definition.feeTier.toFixed(2)}%)`;
return label;
}

return `[${definition.exchange}] ${definition.poolName}`;
}

getKey({ contractPosition }: { contractPosition: ContractPosition<HomoraV2FarmingPositionDataProps> }) {
return this.appToolkit.getPositionKey(contractPosition, ['feeTier']);
}

async getNativeToken() {
const baseTokens = await this.appToolkit.getBaseTokenPrices(this.network);
const nativeToken = baseTokens.find(t => t.address === ZERO_ADDRESS);

return nativeToken;
}

async getPools() {
const chainId = NETWORK_IDS[this.network];
const { data } = await httpClient.get<Pool[]>(`${chainId}/pools`);
return data;
}

async getTradingVolumns() {
const chainId = NETWORK_IDS[this.network];
const { data } = await httpClient.get<Record<string, string>>(`${chainId}/trading-volumes`);
return data;
}

// @ts-ignore
async getTokenBalancesPerPosition() {
throw new NotImplementedException();
}
}
153 changes: 153 additions & 0 deletions src/apps/homora-v2/common/homora-v2.lending.token-position-fetcher.ts
@@ -0,0 +1,153 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher';
import {
GetDataPropsParams,
GetDisplayPropsParams,
GetTokenPropsParams,
GetUnderlyingTokensParams,
} from '~position/template/app-token.template.types';
import { NETWORK_IDS } from '~types';

import { HomoraV2ContractFactory } from '../contracts';
import { CyToken } from '../contracts/ethers/CyToken';
import httpClient from '../helpers/httpClient';
import { SafeboxStatus } from '../interfaces/enums';
import { HomoraV2LendingPositionDataProps, HomoraV2LendingPositionDefinition, Safebox } from '../interfaces/interfaces';

export abstract class HomoraV2LendingTokenFetcher extends AppTokenTemplatePositionFetcher<
CyToken,
HomoraV2LendingPositionDataProps,
HomoraV2LendingPositionDefinition
> {
constructor(
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
@Inject(HomoraV2ContractFactory) protected readonly contractFactory: HomoraV2ContractFactory,
) {
super(appToolkit);
}

getContract(address: string): CyToken {
return this.contractFactory.cyToken({ address, network: this.network });
}

async getAddresses(): Promise<string[]> {
const safeboxes = await this.getSafeboxes();
return safeboxes
.filter(safebox => ![SafeboxStatus.delisted, SafeboxStatus.delisting].includes(safebox.status!))
.map(safebox => safebox.cyTokenAddress);
}

async getDefinitions() {
const addresses = await this.getSafeboxes();
return addresses.map(({ address, cyTokenAddress, safeboxAddress }) => ({
tokenAddress: address,
address: cyTokenAddress,
safeboxAddress,
}));
}

async getUnderlyingTokenAddresses({ contract }: GetUnderlyingTokensParams<CyToken>) {
return contract.underlying();
}

async getSymbol({
definition,
multicall,
}: GetTokenPropsParams<CyToken, HomoraV2LendingPositionDefinition>): Promise<string> {
const tokenAddress = definition.tokenAddress;
const erc20 = this.appToolkit.globalContracts.erc20({ address: tokenAddress, network: this.network });
return multicall.wrap(erc20).symbol();
}

async getDecimals({
definition,
multicall,
}: GetTokenPropsParams<CyToken, HomoraV2LendingPositionDefinition>): Promise<number> {
const tokenAddress = definition.tokenAddress;
const erc20 = this.appToolkit.globalContracts.erc20({ address: tokenAddress, network: this.network });
return multicall.wrap(erc20).decimals();
}

async getSupply({ contract, multicall }: GetTokenPropsParams<CyToken, HomoraV2LendingPositionDefinition>) {
const [currentCash, borrow] = await Promise.all([
multicall.wrap(contract).getCash(),
multicall.wrap(contract).totalBorrows(),
]);

const totalSupply = currentCash.add(borrow);
return totalSupply;
}

getLiquidity({ appToken }: GetDataPropsParams<CyToken>) {
return appToken.supply * appToken.price;
}

getReserves({ appToken }: GetDataPropsParams<CyToken>) {
return [appToken.pricePerShare[0] * appToken.supply];
}

async getApy(): Promise<number> {
return 0;
}

async getDataProps(
params: GetDataPropsParams<CyToken, HomoraV2LendingPositionDataProps, HomoraV2LendingPositionDefinition>,
) {
const { definition, contract, multicall, appToken } = params;
const tokenAddress = definition.tokenAddress;
const erc20 = this.appToolkit.globalContracts.erc20({ address: tokenAddress, network: this.network });
const [borrow, decimals] = await Promise.all([
multicall.wrap(contract).totalBorrows(),
multicall.wrap(erc20).decimals(),
]);

const supply = appToken.supply;
const _borrow = Number(borrow) / 10 ** decimals;

const utilization = (_borrow / (supply === 0 ? 1 : supply)) * 100; // Prevent 0/0

const [liquidity, reserves, apy] = await Promise.all([
this.getLiquidity(params),
this.getReserves(params),
this.getApy(),
]);

return {
supply: Number(supply),
borrow: Number(_borrow),
utilization: utilization.toFixed(2),
liquidity,
reserves,
apy,
};
}

async getLabel({
appToken,
}: GetDisplayPropsParams<
CyToken,
HomoraV2LendingPositionDataProps,
HomoraV2LendingPositionDefinition
>): Promise<string> {
return getLabelFromToken(appToken.tokens[0]);
}

async getLabelDetailed({
appToken,
}: GetDisplayPropsParams<
CyToken,
HomoraV2LendingPositionDataProps,
HomoraV2LendingPositionDefinition
>): Promise<string> {
return appToken.symbol;
}

async getSafeboxes() {
const chainId = NETWORK_IDS[this.network];
const { data } = await httpClient.get<Safebox[]>(`${chainId}/safeboxes`);
return data;
}
}

0 comments on commit 7fbd35e

Please sign in to comment.