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

Commit

Permalink
feat(notional-finance-v3): Added fCash, pCash, pDebt, nToken and lend…
Browse files Browse the repository at this point in the history
…ing markets (#3009)

* feat(notional-finance-v3): Added fCash, pCash, pDebt, nToken and lending markets

* fix(notional-finance-v3): Add logo
  • Loading branch information
wpoulin authored Nov 4, 2023
1 parent 6a185b5 commit d7b1e0e
Show file tree
Hide file tree
Showing 21 changed files with 29,004 additions and 0 deletions.
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()];
}
}
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);
}
}
Loading

0 comments on commit d7b1e0e

Please sign in to comment.