From 8199a2df0b6c32d34ac50af598fa667f51f66afd Mon Sep 17 00:00:00 2001 From: immasandwich Date: Sat, 28 May 2022 16:25:11 -0400 Subject: [PATCH] feat(cozy-finance): Support Cozy Finance (#534) --- .../helper/compound.supply.token-helper.ts | 4 +- .../arbitrum/cozy-finance.balance-fetcher.ts | 70 ++++++++++++++++++ ...inance.borrow.contract-position-fetcher.ts | 30 ++++++++ .../cozy-finance.supply.token-fetcher.ts | 45 +++++++++++ src/apps/cozy-finance/assets/logo.png | Bin 0 -> 6854 bytes src/apps/cozy-finance/contracts/index.ts | 14 ++++ .../cozy-finance/cozy-finance.definition.ts | 45 +++++++++++ src/apps/cozy-finance/cozy-finance.module.ts | 30 ++++++++ .../ethereum/cozy-finance.balance-fetcher.ts | 70 ++++++++++++++++++ ...inance.borrow.contract-position-fetcher.ts | 30 ++++++++ .../cozy-finance.supply.token-fetcher.ts | 45 +++++++++++ src/apps/cozy-finance/index.ts | 3 + 12 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 src/apps/cozy-finance/arbitrum/cozy-finance.balance-fetcher.ts create mode 100644 src/apps/cozy-finance/arbitrum/cozy-finance.borrow.contract-position-fetcher.ts create mode 100644 src/apps/cozy-finance/arbitrum/cozy-finance.supply.token-fetcher.ts create mode 100644 src/apps/cozy-finance/assets/logo.png create mode 100644 src/apps/cozy-finance/contracts/index.ts create mode 100644 src/apps/cozy-finance/cozy-finance.definition.ts create mode 100644 src/apps/cozy-finance/cozy-finance.module.ts create mode 100644 src/apps/cozy-finance/ethereum/cozy-finance.balance-fetcher.ts create mode 100644 src/apps/cozy-finance/ethereum/cozy-finance.borrow.contract-position-fetcher.ts create mode 100644 src/apps/cozy-finance/ethereum/cozy-finance.supply.token-fetcher.ts create mode 100644 src/apps/cozy-finance/index.ts diff --git a/src/apps/compound/helper/compound.supply.token-helper.ts b/src/apps/compound/helper/compound.supply.token-helper.ts index ac3621d56..3bc2f5c61 100644 --- a/src/apps/compound/helper/compound.supply.token-helper.ts +++ b/src/apps/compound/helper/compound.supply.token-helper.ts @@ -3,7 +3,7 @@ import { BigNumberish } from 'ethers'; import _ from 'lodash'; import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { ZERO_ADDRESS } from '~app-toolkit/constants/address'; +import { ETH_ADDR_ALIAS, ZERO_ADDRESS } from '~app-toolkit/constants/address'; import { BLOCKS_PER_DAY } from '~app-toolkit/constants/blocks'; import { buildDollarDisplayItem, @@ -93,7 +93,7 @@ export class CompoundSupplyTokenHelper { const contract = getTokenContract({ address, network }); const underlyingAddress = await getUnderlyingAddress({ contract, multicall }) - .then(t => t.toLowerCase()) + .then(t => t.toLowerCase().replace(ETH_ADDR_ALIAS, ZERO_ADDRESS)) .catch(() => ZERO_ADDRESS); const underlyingToken = allTokens.find(v => v.address === underlyingAddress); diff --git a/src/apps/cozy-finance/arbitrum/cozy-finance.balance-fetcher.ts b/src/apps/cozy-finance/arbitrum/cozy-finance.balance-fetcher.ts new file mode 100644 index 000000000..c22bdcfa9 --- /dev/null +++ b/src/apps/cozy-finance/arbitrum/cozy-finance.balance-fetcher.ts @@ -0,0 +1,70 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present'; +import { + CompoundBorrowBalanceHelper, + CompoundContractFactory, + CompoundLendingMetaHelper, + CompoundSupplyBalanceHelper, +} from '~apps/compound'; +import { BalanceFetcher } from '~balance/balance-fetcher.interface'; +import { Network } from '~types/network.interface'; + +import { COZY_FINANCE_DEFINITION } from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const network = Network.ARBITRUM_MAINNET; + +@Register.BalanceFetcher(COZY_FINANCE_DEFINITION.id, network) +export class ArbitrumCozyFinanceBalanceFetcher implements BalanceFetcher { + constructor( + @Inject(CompoundBorrowBalanceHelper) + private readonly compoundBorrowBalanceHelper: CompoundBorrowBalanceHelper, + @Inject(CompoundSupplyBalanceHelper) + private readonly compoundSupplyBalanceHelper: CompoundSupplyBalanceHelper, + @Inject(CompoundLendingMetaHelper) + private readonly compoundLendingMetaHelper: CompoundLendingMetaHelper, + @Inject(CompoundContractFactory) + private readonly compoundContractFactory: CompoundContractFactory, + ) {} + + async getSupplyBalances(address: string) { + return this.compoundSupplyBalanceHelper.getBalances({ + address, + appId, + groupId: COZY_FINANCE_DEFINITION.groups.supply.id, + network, + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).balanceOf(address), + }); + } + + async getBorrowBalances(address: string) { + return this.compoundBorrowBalanceHelper.getBalances({ + address, + appId, + groupId: COZY_FINANCE_DEFINITION.groups.borrow.id, + network, + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getBorrowBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).borrowBalanceCurrent(address), + }); + } + + async getBalances(address: string) { + const [supplyBalances, borrowBalances] = await Promise.all([ + this.getSupplyBalances(address), + this.getBorrowBalances(address), + ]); + + const meta = this.compoundLendingMetaHelper.getMeta({ balances: [...supplyBalances, ...borrowBalances] }); + + return presentBalanceFetcherResponse([ + { + label: 'Lending', + assets: [...supplyBalances, ...borrowBalances], + meta, + }, + ]); + } +} diff --git a/src/apps/cozy-finance/arbitrum/cozy-finance.borrow.contract-position-fetcher.ts b/src/apps/cozy-finance/arbitrum/cozy-finance.borrow.contract-position-fetcher.ts new file mode 100644 index 000000000..22a2d825f --- /dev/null +++ b/src/apps/cozy-finance/arbitrum/cozy-finance.borrow.contract-position-fetcher.ts @@ -0,0 +1,30 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { CompoundBorrowContractPositionHelper } from '~apps/compound'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { ContractPosition } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import { COZY_FINANCE_DEFINITION } from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const groupId = COZY_FINANCE_DEFINITION.groups.borrow.id; +const network = Network.ARBITRUM_MAINNET; + +@Register.ContractPositionFetcher({ appId, groupId, network }) +export class ArbitrumCozyFinanceBorrowContractPositionFetcher implements PositionFetcher { + constructor( + @Inject(CompoundBorrowContractPositionHelper) + private readonly compoundBorrowContractPositionHelper: CompoundBorrowContractPositionHelper, + ) {} + + async getPositions() { + return this.compoundBorrowContractPositionHelper.getPositions({ + network, + appId, + groupId, + supplyGroupId: COZY_FINANCE_DEFINITION.groups.supply.id, + }); + } +} diff --git a/src/apps/cozy-finance/arbitrum/cozy-finance.supply.token-fetcher.ts b/src/apps/cozy-finance/arbitrum/cozy-finance.supply.token-fetcher.ts new file mode 100644 index 000000000..897946875 --- /dev/null +++ b/src/apps/cozy-finance/arbitrum/cozy-finance.supply.token-fetcher.ts @@ -0,0 +1,45 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { CompoundContractFactory, CompoundSupplyTokenHelper } from '~apps/compound'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { AppTokenPosition } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import COZY_FINANCE_DEFINITION from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const groupId = COZY_FINANCE_DEFINITION.groups.supply.id; +const network = Network.ARBITRUM_MAINNET; + +@Register.TokenPositionFetcher({ appId, groupId, network }) +export class ArbitrumCozyFinanceSupplyTokenFetcher implements PositionFetcher { + constructor( + @Inject(CompoundContractFactory) private readonly compoundContractFactory: CompoundContractFactory, + @Inject(CompoundSupplyTokenHelper) private readonly compoundSupplyTokenHelper: CompoundSupplyTokenHelper, + ) {} + + async getPositions() { + return this.compoundSupplyTokenHelper.getTokens({ + network, + appId, + groupId, + comptrollerAddress: '0x895879b2c1fbb6ccfcd101f2d3f3c76363664f92', + getComptrollerContract: ({ address, network }) => + this.compoundContractFactory.compoundComptroller({ address, network }), + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getAllMarkets: ({ contract, multicall }) => multicall.wrap(contract).getAllMarkets(), + getExchangeRate: ({ contract, multicall }) => multicall.wrap(contract).exchangeRateCurrent(), + getSupplyRate: ({ contract, multicall }) => multicall.wrap(contract).supplyRatePerBlock(), + getBorrowRate: ({ contract, multicall }) => multicall.wrap(contract).borrowRatePerBlock(), + getUnderlyingAddress: ({ contract, multicall }) => multicall.wrap(contract).underlying(), + getExchangeRateMantissa: ({ underlyingTokenDecimals }) => underlyingTokenDecimals + 10, + getDisplayLabel: async ({ contract, multicall, underlyingToken }) => { + const [symbol, name] = await Promise.all([multicall.wrap(contract).symbol(), multicall.wrap(contract).name()]); + if (!name.startsWith(`${symbol}-`)) return underlyingToken.symbol; + const triggerLabel = name.replace(`${symbol}-`, ''); + return `${underlyingToken.symbol} - ${triggerLabel}`; + }, + }); + } +} diff --git a/src/apps/cozy-finance/assets/logo.png b/src/apps/cozy-finance/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f487c5a15113c37d88fc67dbb289bf38aa25c998 GIT binary patch literal 6854 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 y{{c4toBDr1oKg^e5_0|phnxCjBq0tC7Ryu2+E; appToolkit.getNetworkProvider(network)); + } +} diff --git a/src/apps/cozy-finance/cozy-finance.definition.ts b/src/apps/cozy-finance/cozy-finance.definition.ts new file mode 100644 index 000000000..016db9b07 --- /dev/null +++ b/src/apps/cozy-finance/cozy-finance.definition.ts @@ -0,0 +1,45 @@ +import { Register } from '~app-toolkit/decorators'; +import { appDefinition, AppDefinition } from '~app/app.definition'; +import { AppAction, AppTag, GroupType } from '~app/app.interface'; +import { Network } from '~types/network.interface'; + +export const COZY_FINANCE_DEFINITION = appDefinition({ + id: 'cozy-finance', + name: 'Cozy Finance', + description: 'Cozy is an autonomous protocol for protecting DeFi deposits.', + url: 'https://www.cozy.finance/', + + groups: { + supply: { + id: 'supply', + type: GroupType.TOKEN, + label: 'Supply', + }, + + borrow: { + id: 'borrow', + type: GroupType.TOKEN, + label: 'Borrow', + }, + }, + + tags: [AppTag.INSURANCE], + keywords: [], + links: {}, + + supportedNetworks: { + [Network.ETHEREUM_MAINNET]: [AppAction.VIEW], + [Network.ARBITRUM_MAINNET]: [AppAction.VIEW], + }, + + primaryColor: '#fff', +}); + +@Register.AppDefinition(COZY_FINANCE_DEFINITION.id) +export class CozyFinanceAppDefinition extends AppDefinition { + constructor() { + super(COZY_FINANCE_DEFINITION); + } +} + +export default COZY_FINANCE_DEFINITION; diff --git a/src/apps/cozy-finance/cozy-finance.module.ts b/src/apps/cozy-finance/cozy-finance.module.ts new file mode 100644 index 000000000..d6a89abf6 --- /dev/null +++ b/src/apps/cozy-finance/cozy-finance.module.ts @@ -0,0 +1,30 @@ +import { Register } from '~app-toolkit/decorators'; +import { AbstractApp } from '~app/app.dynamic-module'; +import { CompoundAppModule } from '~apps/compound'; + +import { ArbitrumCozyFinanceBalanceFetcher } from './arbitrum/cozy-finance.balance-fetcher'; +import { ArbitrumCozyFinanceBorrowContractPositionFetcher } from './arbitrum/cozy-finance.borrow.contract-position-fetcher'; +import { ArbitrumCozyFinanceSupplyTokenFetcher } from './arbitrum/cozy-finance.supply.token-fetcher'; +import { CozyFinanceContractFactory } from './contracts'; +import { CozyFinanceAppDefinition, COZY_FINANCE_DEFINITION } from './cozy-finance.definition'; +import { EthereumCozyFinanceBalanceFetcher } from './ethereum/cozy-finance.balance-fetcher'; +import { EthereumCozyFinanceBorrowContractPositionFetcher } from './ethereum/cozy-finance.borrow.contract-position-fetcher'; +import { EthereumCozyFinanceSupplyTokenFetcher } from './ethereum/cozy-finance.supply.token-fetcher'; + +@Register.AppModule({ + appId: COZY_FINANCE_DEFINITION.id, + imports: [CompoundAppModule], + providers: [ + CozyFinanceAppDefinition, + CozyFinanceContractFactory, + // Arbitrum + ArbitrumCozyFinanceBalanceFetcher, + ArbitrumCozyFinanceBorrowContractPositionFetcher, + ArbitrumCozyFinanceSupplyTokenFetcher, + // Ethereum + EthereumCozyFinanceBalanceFetcher, + EthereumCozyFinanceBorrowContractPositionFetcher, + EthereumCozyFinanceSupplyTokenFetcher, + ], +}) +export class CozyFinanceAppModule extends AbstractApp() {} diff --git a/src/apps/cozy-finance/ethereum/cozy-finance.balance-fetcher.ts b/src/apps/cozy-finance/ethereum/cozy-finance.balance-fetcher.ts new file mode 100644 index 000000000..efd27f91b --- /dev/null +++ b/src/apps/cozy-finance/ethereum/cozy-finance.balance-fetcher.ts @@ -0,0 +1,70 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present'; +import { + CompoundBorrowBalanceHelper, + CompoundContractFactory, + CompoundLendingMetaHelper, + CompoundSupplyBalanceHelper, +} from '~apps/compound'; +import { BalanceFetcher } from '~balance/balance-fetcher.interface'; +import { Network } from '~types/network.interface'; + +import { COZY_FINANCE_DEFINITION } from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.BalanceFetcher(COZY_FINANCE_DEFINITION.id, network) +export class EthereumCozyFinanceBalanceFetcher implements BalanceFetcher { + constructor( + @Inject(CompoundBorrowBalanceHelper) + private readonly compoundBorrowBalanceHelper: CompoundBorrowBalanceHelper, + @Inject(CompoundSupplyBalanceHelper) + private readonly compoundSupplyBalanceHelper: CompoundSupplyBalanceHelper, + @Inject(CompoundLendingMetaHelper) + private readonly compoundLendingMetaHelper: CompoundLendingMetaHelper, + @Inject(CompoundContractFactory) + private readonly compoundContractFactory: CompoundContractFactory, + ) {} + + async getSupplyBalances(address: string) { + return this.compoundSupplyBalanceHelper.getBalances({ + address, + appId, + groupId: COZY_FINANCE_DEFINITION.groups.supply.id, + network, + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).balanceOf(address), + }); + } + + async getBorrowBalances(address: string) { + return this.compoundBorrowBalanceHelper.getBalances({ + address, + appId, + groupId: COZY_FINANCE_DEFINITION.groups.borrow.id, + network, + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getBorrowBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).borrowBalanceCurrent(address), + }); + } + + async getBalances(address: string) { + const [supplyBalances, borrowBalances] = await Promise.all([ + this.getSupplyBalances(address), + this.getBorrowBalances(address), + ]); + + const meta = this.compoundLendingMetaHelper.getMeta({ balances: [...supplyBalances, ...borrowBalances] }); + + return presentBalanceFetcherResponse([ + { + label: 'Lending', + assets: [...supplyBalances, ...borrowBalances], + meta, + }, + ]); + } +} diff --git a/src/apps/cozy-finance/ethereum/cozy-finance.borrow.contract-position-fetcher.ts b/src/apps/cozy-finance/ethereum/cozy-finance.borrow.contract-position-fetcher.ts new file mode 100644 index 000000000..956fd88bd --- /dev/null +++ b/src/apps/cozy-finance/ethereum/cozy-finance.borrow.contract-position-fetcher.ts @@ -0,0 +1,30 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { CompoundBorrowContractPositionHelper } from '~apps/compound'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { ContractPosition } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import { COZY_FINANCE_DEFINITION } from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const groupId = COZY_FINANCE_DEFINITION.groups.borrow.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.ContractPositionFetcher({ appId, groupId, network }) +export class EthereumCozyFinanceBorrowContractPositionFetcher implements PositionFetcher { + constructor( + @Inject(CompoundBorrowContractPositionHelper) + private readonly compoundBorrowContractPositionHelper: CompoundBorrowContractPositionHelper, + ) {} + + async getPositions() { + return this.compoundBorrowContractPositionHelper.getPositions({ + network, + appId, + groupId, + supplyGroupId: COZY_FINANCE_DEFINITION.groups.supply.id, + }); + } +} diff --git a/src/apps/cozy-finance/ethereum/cozy-finance.supply.token-fetcher.ts b/src/apps/cozy-finance/ethereum/cozy-finance.supply.token-fetcher.ts new file mode 100644 index 000000000..92885a68e --- /dev/null +++ b/src/apps/cozy-finance/ethereum/cozy-finance.supply.token-fetcher.ts @@ -0,0 +1,45 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { CompoundContractFactory, CompoundSupplyTokenHelper } from '~apps/compound'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { AppTokenPosition } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import COZY_FINANCE_DEFINITION from '../cozy-finance.definition'; + +const appId = COZY_FINANCE_DEFINITION.id; +const groupId = COZY_FINANCE_DEFINITION.groups.supply.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.TokenPositionFetcher({ appId, groupId, network }) +export class EthereumCozyFinanceSupplyTokenFetcher implements PositionFetcher { + constructor( + @Inject(CompoundContractFactory) private readonly compoundContractFactory: CompoundContractFactory, + @Inject(CompoundSupplyTokenHelper) private readonly compoundSupplyTokenHelper: CompoundSupplyTokenHelper, + ) {} + + async getPositions() { + return this.compoundSupplyTokenHelper.getTokens({ + network, + appId, + groupId, + comptrollerAddress: '0x895879b2c1fbb6ccfcd101f2d3f3c76363664f92', + getComptrollerContract: ({ address, network }) => + this.compoundContractFactory.compoundComptroller({ address, network }), + getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }), + getAllMarkets: ({ contract, multicall }) => multicall.wrap(contract).getAllMarkets(), + getExchangeRate: ({ contract, multicall }) => multicall.wrap(contract).exchangeRateCurrent(), + getSupplyRate: ({ contract, multicall }) => multicall.wrap(contract).supplyRatePerBlock(), + getBorrowRate: ({ contract, multicall }) => multicall.wrap(contract).borrowRatePerBlock(), + getUnderlyingAddress: ({ contract, multicall }) => multicall.wrap(contract).underlying(), + getExchangeRateMantissa: ({ underlyingTokenDecimals }) => underlyingTokenDecimals + 10, + getDisplayLabel: async ({ contract, multicall, underlyingToken }) => { + const [symbol, name] = await Promise.all([multicall.wrap(contract).symbol(), multicall.wrap(contract).name()]); + if (!name.startsWith(`${symbol}-`)) return underlyingToken.symbol; + const triggerLabel = name.replace(`${symbol}-`, ''); + return `${underlyingToken.symbol} - ${triggerLabel}`; + }, + }); + } +} diff --git a/src/apps/cozy-finance/index.ts b/src/apps/cozy-finance/index.ts new file mode 100644 index 000000000..6926d53ae --- /dev/null +++ b/src/apps/cozy-finance/index.ts @@ -0,0 +1,3 @@ +export { COZY_FINANCE_DEFINITION, CozyFinanceAppDefinition } from './cozy-finance.definition'; +export { CozyFinanceAppModule } from './cozy-finance.module'; +export { CozyFinanceContractFactory } from './contracts';