This repository has been archived by the owner on Jan 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 381
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(notional-finance-v3): Added fCash, pCash, pDebt, nToken and lend…
…ing markets (#3009) * feat(notional-finance-v3): Added fCash, pCash, pDebt, nToken and lending markets * fix(notional-finance-v3): Add logo
- Loading branch information
Showing
21 changed files
with
29,004 additions
and
0 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
...apps/notional-finance-v3/arbitrum/notional-finance-v3.borrow.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,133 @@ | ||
import { Inject } from '@nestjs/common'; | ||
import BigNumber from 'bignumber.js'; | ||
import { pick, range } from 'lodash'; | ||
|
||
import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; | ||
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator'; | ||
import { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present'; | ||
import { MetaType } from '~position/position.interface'; | ||
import { GetDefinitionsParams } from '~position/template/app-token.template.types'; | ||
import { ContractPositionTemplatePositionFetcher } from '~position/template/contract-position.template.position-fetcher'; | ||
import { | ||
GetDataPropsParams, | ||
GetDisplayPropsParams, | ||
GetTokenBalancesParams, | ||
GetTokenDefinitionsParams, | ||
} from '~position/template/contract-position.template.types'; | ||
|
||
import { NotionalFinanceV3ContractFactory, NotionalView } from '../contracts'; | ||
|
||
export type NotionalBorrowingDefinition = { | ||
address: string; | ||
underlyingTokenAddress: string; | ||
currencyId: number; | ||
tokenId: string; | ||
maturity: number; | ||
type: string; | ||
}; | ||
|
||
export type NotionalBorrowingDataProps = { | ||
currencyId: number; | ||
tokenId: string; | ||
maturity: number; | ||
type: string; | ||
}; | ||
|
||
@PositionTemplate() | ||
export class EthereumNotionalFinanceV3BorrowContractPositionFetcher extends ContractPositionTemplatePositionFetcher< | ||
NotionalView, | ||
NotionalBorrowingDataProps, | ||
NotionalBorrowingDefinition | ||
> { | ||
groupLabel = 'Borrow'; | ||
|
||
notionalViewContractAddress = '0x1344a36a1b56144c3bc62e7757377d288fde0369'; | ||
|
||
constructor( | ||
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit, | ||
@Inject(NotionalFinanceV3ContractFactory) protected readonly contractFactory: NotionalFinanceV3ContractFactory, | ||
) { | ||
super(appToolkit); | ||
} | ||
|
||
getContract(address: string): NotionalView { | ||
return this.contractFactory.notionalView({ address, network: this.network }); | ||
} | ||
|
||
async getDefinitions({ multicall }: GetDefinitionsParams): Promise<NotionalBorrowingDefinition[]> { | ||
const notionalViewContract = this.contractFactory.notionalView({ | ||
address: this.notionalViewContractAddress, | ||
network: this.network, | ||
}); | ||
|
||
const currencyCount = await multicall.wrap(notionalViewContract).getMaxCurrencyId(); | ||
const currencyRange = range(1, currencyCount + 1); | ||
const definitions = await Promise.all( | ||
currencyRange.map(async currencyId => { | ||
const currency = await multicall.wrap(notionalViewContract).getCurrency(currencyId); | ||
const underlyingTokenAddress = currency.underlyingToken.tokenAddress.toLowerCase(); | ||
const activeMarkets = await multicall.wrap(notionalViewContract).getActiveMarkets(currencyId); | ||
|
||
const markets = await Promise.all( | ||
activeMarkets.map(async activeMarket => { | ||
const type = 'borrowing'; | ||
const maturity = Number(activeMarket.maturity); | ||
const tokenId = await multicall | ||
.wrap(notionalViewContract) | ||
.encodeToId(currencyId, maturity, 0) | ||
.then(v => v.toString()); | ||
|
||
return { | ||
address: this.notionalViewContractAddress, | ||
currencyId, | ||
underlyingTokenAddress, | ||
maturity, | ||
tokenId, | ||
type, | ||
}; | ||
}), | ||
); | ||
|
||
return markets; | ||
}), | ||
); | ||
|
||
return definitions.flat(); | ||
} | ||
|
||
async getTokenDefinitions({ definition }: GetTokenDefinitionsParams<NotionalView, NotionalBorrowingDefinition>) { | ||
return [ | ||
{ metaType: MetaType.BORROWED, address: definition.address, network: this.network, tokenId: definition.tokenId }, | ||
]; | ||
} | ||
|
||
async getDataProps( | ||
params: GetDataPropsParams<NotionalView, NotionalBorrowingDataProps, NotionalBorrowingDefinition>, | ||
) { | ||
const defaultDataProps = await super.getDataProps(params); | ||
const props = pick(params.definition, ['currencyId', 'tokenId', 'maturity', 'type']); | ||
return { ...defaultDataProps, ...props, positionKey: Object.values(props).join(':') }; | ||
} | ||
|
||
async getLabel({ contractPosition }: GetDisplayPropsParams<NotionalView>): Promise<string> { | ||
return getLabelFromToken(contractPosition.tokens[0]); | ||
} | ||
|
||
async getTokenBalancesPerPosition({ | ||
address, | ||
contractPosition, | ||
contract, | ||
}: GetTokenBalancesParams<NotionalView, NotionalBorrowingDataProps>) { | ||
const { maturity, currencyId } = contractPosition.dataProps; | ||
const portfolio = await contract.getAccountPortfolio(address); | ||
const debtPositions = portfolio.filter(v => v.notional.isNegative()); | ||
const position = debtPositions.find(v => Number(v.maturity) === maturity && Number(v.currencyId) === currencyId); | ||
if (!position) return [0]; | ||
|
||
const fcashAmountAtMaturity = new BigNumber(position.notional.toString()) | ||
.times(10 ** contractPosition.tokens[0].decimals) | ||
.div(10 ** 8); | ||
|
||
return [fcashAmountAtMaturity.abs().toString()]; | ||
} | ||
} |
197 changes: 197 additions & 0 deletions
197
src/apps/notional-finance-v3/arbitrum/notional-finance-v3.f-cash.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,197 @@ | ||
import { Inject } from '@nestjs/common'; | ||
import { BigNumberish } from 'ethers'; | ||
import { pick, range } from 'lodash'; | ||
|
||
import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; | ||
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator'; | ||
import { IMulticallWrapper } from '~multicall'; | ||
import { AppTokenPosition } from '~position/position.interface'; | ||
import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher'; | ||
import { | ||
DefaultAppTokenDataProps, | ||
GetAddressesParams, | ||
GetDataPropsParams, | ||
GetDefinitionsParams, | ||
GetDisplayPropsParams, | ||
GetPricePerShareParams, | ||
GetTokenPropsParams, | ||
GetUnderlyingTokensParams, | ||
} from '~position/template/app-token.template.types'; | ||
|
||
import { NotionalFCash, NotionalFinanceV3ContractFactory } from '../contracts'; | ||
|
||
export type NotionalFCashTokenDefinition = { | ||
address: string; | ||
underlyingTokenAddress: string; | ||
currencyId: number; | ||
tokenId: string; | ||
maturity: number; | ||
}; | ||
|
||
export type NotionalFCashTokenDataProps = DefaultAppTokenDataProps & { | ||
currencyId: number; | ||
tokenId: string; | ||
maturity: number; | ||
positionKey: string; | ||
}; | ||
|
||
const NOTIONAL_CONSTANT = 10 ** 7; | ||
const SECONDS_IN_YEAR = 31104000; // 360 days | ||
|
||
@PositionTemplate() | ||
export class ArbitrumNotionalFinanceV3FCashTokenFetcher extends AppTokenTemplatePositionFetcher< | ||
NotionalFCash, | ||
NotionalFCashTokenDataProps, | ||
NotionalFCashTokenDefinition | ||
> { | ||
groupLabel = 'fCash'; | ||
|
||
notionalViewContractAddress = '0x1344a36a1b56144c3bc62e7757377d288fde0369'; | ||
|
||
constructor( | ||
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit, | ||
@Inject(NotionalFinanceV3ContractFactory) protected readonly contractFactory: NotionalFinanceV3ContractFactory, | ||
) { | ||
super(appToolkit); | ||
} | ||
|
||
getContract(address: string): NotionalFCash { | ||
return this.contractFactory.notionalFCash({ address, network: this.network }); | ||
} | ||
|
||
async getDefinitions({ multicall }: GetDefinitionsParams): Promise<NotionalFCashTokenDefinition[]> { | ||
const notionalViewContract = this.contractFactory.notionalView({ | ||
address: this.notionalViewContractAddress, | ||
network: this.network, | ||
}); | ||
|
||
const currencyCount = await multicall.wrap(notionalViewContract).getMaxCurrencyId(); | ||
const currencyRange = range(1, currencyCount + 1); | ||
const definitions = await Promise.all( | ||
currencyRange.map(async currencyId => { | ||
const currency = await multicall.wrap(notionalViewContract).getCurrency(currencyId); | ||
const underlyingTokenAddress = currency.underlyingToken.tokenAddress.toLowerCase(); | ||
const activeMarkets = await multicall.wrap(notionalViewContract).getActiveMarkets(currencyId); | ||
|
||
const markets = await Promise.all( | ||
activeMarkets.map(async activeMarket => { | ||
const maturity = Number(activeMarket.maturity); | ||
const tokenId = await multicall | ||
.wrap(notionalViewContract) | ||
.encodeToId(currencyId, maturity, 0) | ||
.then(v => v.toString()); | ||
|
||
return { | ||
address: this.notionalViewContractAddress, | ||
currencyId, | ||
underlyingTokenAddress, | ||
maturity, | ||
tokenId, | ||
}; | ||
}), | ||
); | ||
|
||
return markets; | ||
}), | ||
); | ||
|
||
return definitions.flat(); | ||
} | ||
|
||
async getAddresses({ definitions }: GetAddressesParams<NotionalFCashTokenDefinition>) { | ||
return definitions.map(v => v.address); | ||
} | ||
|
||
async getSymbol({ appToken }: GetTokenPropsParams<NotionalFCash>) { | ||
return `f${appToken.tokens[0].symbol}`; | ||
} | ||
|
||
async getSupply() { | ||
return 0; | ||
} | ||
|
||
async getDecimals({ appToken }: GetTokenPropsParams<NotionalFCash, NotionalFCashTokenDataProps>) { | ||
return appToken.tokens[0].decimals; | ||
} | ||
|
||
async getUnderlyingTokenDefinitions({ | ||
definition, | ||
}: GetUnderlyingTokensParams<NotionalFCash, NotionalFCashTokenDefinition>) { | ||
return [{ address: definition.underlyingTokenAddress, network: this.network }]; | ||
} | ||
|
||
async getPricePerShare({ | ||
definition, | ||
multicall, | ||
}: GetPricePerShareParams<NotionalFCash, NotionalFCashTokenDataProps, NotionalFCashTokenDefinition>) { | ||
const notionalViewContract = this.contractFactory.notionalView({ | ||
address: this.notionalViewContractAddress, | ||
network: this.network, | ||
}); | ||
|
||
const activeMarkets = await multicall.wrap(notionalViewContract).getActiveMarkets(definition.currencyId); | ||
const market = activeMarkets.find(v => Number(v.maturity) === definition.maturity); | ||
const apy = Number(market?.lastImpliedRate ?? 0) / NOTIONAL_CONSTANT; | ||
|
||
const dateNowEpoch = Date.now() / 1000; | ||
const timeToMaturity = (definition.maturity - dateNowEpoch) / SECONDS_IN_YEAR; | ||
const lastImpliedRate = apy / 100; | ||
const fCashPV = 1 / Math.exp(lastImpliedRate * timeToMaturity); | ||
|
||
return [fCashPV]; | ||
} | ||
|
||
async getLiquidity({ appToken }: GetDataPropsParams<NotionalFCash>) { | ||
return appToken.supply * appToken.price; | ||
} | ||
|
||
async getReserves() { | ||
return [0]; | ||
} | ||
|
||
async getApy({ | ||
definition, | ||
multicall, | ||
}: GetDataPropsParams<NotionalFCash, NotionalFCashTokenDataProps, NotionalFCashTokenDefinition>) { | ||
const notionalViewContract = this.contractFactory.notionalView({ | ||
address: this.notionalViewContractAddress, | ||
network: this.network, | ||
}); | ||
|
||
const activeMarkets = await multicall.wrap(notionalViewContract).getActiveMarkets(definition.currencyId); | ||
const market = activeMarkets.find(v => Number(v.maturity) === definition.maturity); | ||
return Number(market?.lastImpliedRate ?? 0) / NOTIONAL_CONSTANT; | ||
} | ||
|
||
async getDataProps( | ||
params: GetDataPropsParams<NotionalFCash, NotionalFCashTokenDataProps, NotionalFCashTokenDefinition>, | ||
) { | ||
const defaultDataProps = await super.getDataProps(params); | ||
return { | ||
...defaultDataProps, | ||
...pick(params.definition, ['currencyId', 'tokenId', 'maturity']), | ||
positionKey: params.definition.tokenId, | ||
}; | ||
} | ||
|
||
async getLabel({ appToken }: GetDisplayPropsParams<NotionalFCash, NotionalFCashTokenDataProps>) { | ||
const maturityDate = new Date(Number(appToken.dataProps.maturity) * 1000); | ||
const year = maturityDate.getFullYear(); | ||
const month = maturityDate.getMonth() + 1; | ||
const day = maturityDate.getDate(); | ||
const displayMaturity = day + '/' + month + '/' + year; | ||
return `${appToken.symbol} - ${displayMaturity}`; | ||
} | ||
|
||
getBalancePerToken({ | ||
address, | ||
appToken, | ||
multicall, | ||
}: { | ||
address: string; | ||
appToken: AppTokenPosition<NotionalFCashTokenDataProps>; | ||
multicall: IMulticallWrapper; | ||
}): Promise<BigNumberish> { | ||
return multicall.wrap(this.getContract(appToken.address)).balanceOf(address, appToken.dataProps.tokenId); | ||
} | ||
} |
Oops, something went wrong.