This repository has been archived by the owner on Jan 24, 2024. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(homora-v2): Add Homora V2 integration (#1596)
- Loading branch information
Showing
28 changed files
with
21,168 additions
and
0 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
src/apps/homora-v2/avalanche/homora-v2.farm.contract-position-fetcher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
13
src/apps/homora-v2/avalanche/homora-v2.lending.token-fetcher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
124
src/apps/homora-v2/common/homora-v2.farm.contract-position-fetcher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
153
src/apps/homora-v2/common/homora-v2.lending.token-position-fetcher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
Oops, something went wrong.