diff --git a/apps/main/src/lend/components/DetailsMarket/components/DetailsContracts.tsx b/apps/main/src/lend/components/DetailsMarket/components/DetailsContracts.tsx index 29f29addfc..94214a4c6b 100644 --- a/apps/main/src/lend/components/DetailsMarket/components/DetailsContracts.tsx +++ b/apps/main/src/lend/components/DetailsMarket/components/DetailsContracts.tsx @@ -2,12 +2,16 @@ import { ReactNode } from 'react' import { zeroAddress } from 'viem' import ChipInactive from '@/lend/components/ChipInactive' import DetailInfoAddressLookup from '@/lend/components/DetailsMarket/components/DetailInfoAddressLookup' -import { SubTitle } from '@/lend/components/DetailsMarket/styles' import TokenLabel from '@/lend/components/TokenLabel' import { PageContentProps } from '@/lend/types/lend.types' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' import Box from '@ui/Box' import Chip from '@ui/Typography/Chip' import { t } from '@ui-kit/lib/i18n' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' + +const { Spacing } = SizesAndSpaces type ContractItems = { label: ReactNode; address: string | undefined; invalidText?: string }[] @@ -31,8 +35,8 @@ const DetailsContracts = ({ rChainId, market }: Pick - {t`Contracts`} + + {t`Contracts`} {contracts.map((contracts, idx) => (
@@ -61,7 +65,7 @@ const DetailsContracts = ({ rChainId, market }: Pick ))} - + ) } diff --git a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx b/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx deleted file mode 100644 index 2c63762164..0000000000 --- a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Fragment, useEffect } from 'react' -import { SubTitle } from '@/lend/components/DetailsMarket/styles' -import { useOneWayMarket } from '@/lend/entities/chain' -import useStore from '@/lend/store/useStore' -import { ChainId } from '@/lend/types/lend.types' -import Box from '@ui/Box' -import DetailInfo from '@ui/DetailInfo' -import Icon from '@ui/Icon' -import Chip from '@ui/Typography/Chip' -import { FORMAT_OPTIONS, formatNumber, NumberFormatOptions } from '@ui/utils' -import { useUserProfileStore } from '@ui-kit/features/user-profile' -import { t } from '@ui-kit/lib/i18n' - -const MarketParameters = ({ - rChainId, - rOwmId, - type, -}: { - rChainId: ChainId - rOwmId: string - type: 'borrow' | 'supply' -}) => { - const owm = useOneWayMarket(rChainId, rOwmId).data - const loanPricesResp = useStore((state) => state.markets.pricesMapper[rChainId]?.[rOwmId]) - const parametersResp = useStore((state) => state.markets.statsParametersMapper[rChainId]?.[rOwmId]) - const vaultPricePerShareResp = useStore((state) => state.markets.vaultPricePerShare[rChainId]?.[rOwmId]) - const fetchVaultPricePerShare = useStore((state) => state.markets.fetchVaultPricePerShare) - - const isAdvancedMode = useUserProfileStore((state) => state.isAdvancedMode) - - const { prices, error: pricesError } = loanPricesResp ?? {} - const { parameters, error: parametersError } = parametersResp ?? {} - const { pricePerShare, error: pricePerShareError } = vaultPricePerShareResp ?? {} - - // prettier-ignore - const marketDetails: { label: string; value: string | number | undefined; formatOptions?: NumberFormatOptions; title?: string; isError: string; isRow?: boolean, isAdvance?: boolean; tooltip?: string }[][] = type === 'borrow' ? - [ - [ - { label: t`AMM swap fee`, value: parameters?.fee, formatOptions: { ...FORMAT_OPTIONS.PERCENT, maximumSignificantDigits: 3 }, isError: parametersError }, - { label: t`Admin fee`, value: parameters?.admin_fee, formatOptions: { ...FORMAT_OPTIONS.PERCENT, maximumSignificantDigits: 3 }, isError: parametersError }, - { label: t`Band width factor`, value: parameters?.A, formatOptions: { useGrouping: false }, isError: parametersError }, - { label: t`Loan discount`, value: parameters?.loan_discount, formatOptions: { ...FORMAT_OPTIONS.PERCENT, maximumSignificantDigits: 2 }, isError: parametersError }, - { label: t`Liquidation discount`, value: parameters?.liquidation_discount, formatOptions: { ...FORMAT_OPTIONS.PERCENT, maximumSignificantDigits: 2 }, isError: parametersError }, - { label: t`Max LTV`, value: _getMaxLTV( parameters?.A, parameters?.loan_discount), formatOptions: { ...FORMAT_OPTIONS.PERCENT, maximumSignificantDigits: 2 }, isError: parametersError, isAdvance: true, tooltip: t`Max possible loan at N=4` }, - ], - [ - { label: t`Base price`, value: prices?.basePrice, formatOptions: { decimals: 5 }, title: t`Prices`, isError: pricesError }, - { label: t`Oracle price`, value: prices?.oraclePrice, formatOptions: { decimals: 5 }, isError: pricesError }, - ], - ] : [ - [ - { label: t`Price per share`, value: pricePerShare, formatOptions: { decimals: 5 }, isRow: true, isError: pricePerShareError }, - ] - ] - - useEffect(() => { - if (type === 'supply' && owm) void fetchVaultPricePerShare(rChainId, owm) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [type, owm]) - - return ( - - {marketDetails.map((details, idx) => ( -
- {details.map(({ label, value, formatOptions, title, isError, isRow, isAdvance, tooltip }) => { - const show = typeof isAdvance === 'undefined' || (isAdvance && isAdvancedMode) - return ( - - {show ? ( - <> - {title && {title}} - {isRow ? ( - - {label}: - {formatNumber(value, { ...(formatOptions ?? {}), defaultValue: '-' })} - - ) : ( - - {isError ? ( - '?' - ) : ( - - {formatNumber(value, { ...(formatOptions ?? {}), defaultValue: '-' })} - {tooltip && } - - )} - - )} - - ) : null} - - ) - })} -
- ))} -
- ) -} - -// In [1]: ltv = lambda x: ((x[0] - 1) / x[0])**2 * (1 - x[1]) -// In [2]: ltv((30, 0.11)) -// Out[2]: 0.8316555555555556 -// where x[0] is A, x[1] is loan discount normalised between 0 and 1 (so 11% is 0.11). multiply ltv by 100 to show percentage. -// always show 'max ltv' which is the max possible loan at N=4 (not advisable but hey it exists!). -function _getMaxLTV(a: string | undefined, loanDiscount: string | undefined) { - if (typeof a === 'undefined' || typeof loanDiscount === 'undefined') return '' - return ((+a - 1) / +a) ** 2 * (1 - +loanDiscount / 100) * 100 -} - -export default MarketParameters diff --git a/apps/main/src/lend/components/DetailsMarket/styles.ts b/apps/main/src/lend/components/DetailsMarket/styles.ts index 2a7d0c7238..0ab30c53a5 100644 --- a/apps/main/src/lend/components/DetailsMarket/styles.ts +++ b/apps/main/src/lend/components/DetailsMarket/styles.ts @@ -1,11 +1,6 @@ import { styled } from 'styled-components' import Box from '@ui/Box' -export const SubTitle = styled.h3` - font-size: var(--font-size-3); - margin-bottom: var(--spacing-1); -` - export type StatsProps = { isBorderBottom?: boolean isMultiLine?: boolean diff --git a/apps/main/src/lend/components/MarketInformationComp.tsx b/apps/main/src/lend/components/MarketInformationComp.tsx index b7d92ce175..931e8e706c 100644 --- a/apps/main/src/lend/components/MarketInformationComp.tsx +++ b/apps/main/src/lend/components/MarketInformationComp.tsx @@ -1,18 +1,17 @@ import { BandsComp } from '@/lend/components/BandsComp' import ChartOhlcWrapper from '@/lend/components/ChartOhlcWrapper' import DetailsContracts from '@/lend/components/DetailsMarket/components/DetailsContracts' -import MarketParameters from '@/lend/components/DetailsMarket/components/MarketParameters' -import { SubTitle } from '@/lend/components/DetailsMarket/styles' import networks from '@/lend/networks' import { PageContentProps } from '@/lend/types/lend.types' import { BandsChart } from '@/llamalend/features/bands-chart/BandsChart' import { useBandsData } from '@/llamalend/features/bands-chart/hooks/useBandsData' import { getBandsChartToken } from '@/llamalend/features/bands-chart/utils' -import { Stack, useTheme } from '@mui/material' +import { MarketParameters } from '@/llamalend/features/market-parameters/MarketParameters' +import { useTheme } from '@mui/material' +import Stack from '@mui/material/Stack' import { getLib } from '@ui-kit/features/connect-wallet' import { useUserProfileStore } from '@ui-kit/features/user-profile' import { useNewBandsChart } from '@ui-kit/hooks/useFeatureFlags' -import { t } from '@ui-kit/lib/i18n' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' const { Spacing } = SizesAndSpaces @@ -108,10 +107,8 @@ export const MarketInformationComp = ({ - t.design.Layer[2].Fill, padding: Spacing.md, minWidth: '18.75rem' }}> - {t`Parameters`} - - + + )} diff --git a/apps/main/src/lend/components/PageLoanCreate/LoanFormCreate/index.tsx b/apps/main/src/lend/components/PageLoanCreate/LoanFormCreate/index.tsx index cf8cef2e9d..05549d74ad 100644 --- a/apps/main/src/lend/components/PageLoanCreate/LoanFormCreate/index.tsx +++ b/apps/main/src/lend/components/PageLoanCreate/LoanFormCreate/index.tsx @@ -1,7 +1,6 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from 'react' import AlertFormError from '@/lend/components/AlertFormError' import AlertLoanSummary from '@/lend/components/AlertLoanSummary' -import MarketParameters from '@/lend/components/DetailsMarket/components/MarketParameters' import DialogFormWarning from '@/lend/components/DialogFormWarning' import InpToken from '@/lend/components/InpToken' import InpTokenBorrow from '@/lend/components/InpTokenBorrow' @@ -20,6 +19,7 @@ import useStore from '@/lend/store/useStore' import { Api, type MarketUrlParams, OneWayMarketTemplate, PageContentProps } from '@/lend/types/lend.types' import { getLoanManagePathname } from '@/lend/utils/utilsRouter' import { DEFAULT_HEALTH_MODE } from '@/llamalend/constants' +import { MarketParameters } from '@/llamalend/features/market-parameters/MarketParameters' import type { HealthMode } from '@/llamalend/llamalend.types' import { useLoanExists } from '@/llamalend/queries/loan-exists' import Accordion from '@ui/Accordion' @@ -440,7 +440,7 @@ const LoanCreate = ({ {!isAdvancedMode && ( {t`Market details`}}> - + )} diff --git a/apps/main/src/lend/entities/market-details.ts b/apps/main/src/lend/entities/market-details.ts index e827ab6156..7baf6e8f52 100644 --- a/apps/main/src/lend/entities/market-details.ts +++ b/apps/main/src/lend/entities/market-details.ts @@ -8,80 +8,44 @@ import type { MarketQuery, MarketParams } from '@ui-kit/lib/model/query/root-key const getLendMarket = (marketId: string) => requireLib('llamaApi').getLendMarket(marketId) -type MarketCapAndAvailable = { - cap: number - available: number -} -type MarketMaxLeverage = { - maxLeverage: string | null -} -type MarketCollateralAmounts = { - collateralAmount: number - borrowedAmount: number -} - -/** - * The purpose of this query is to allow fetching market collateral amounts on chain - * in order to display the most current data when a wallet is connected. - * */ -const _getMarketCapAndAvailable = async ({ marketId }: MarketQuery): Promise => { - const market = getLendMarket(marketId) - const capAndAvailable = await market.stats.capAndAvailable(false, USE_API) - return { - cap: +capAndAvailable.cap, - available: +capAndAvailable.available, - } -} - export const { useQuery: useMarketCapAndAvailable, invalidate: invalidateMarketCapAndAvailable } = queryFactory({ queryKey: (params: MarketParams) => [...rootKeys.market(params), 'marketCapAndAvailable', 'v1'] as const, - queryFn: _getMarketCapAndAvailable, + queryFn: async ({ marketId }: MarketQuery) => { + const market = getLendMarket(marketId) + const capAndAvailable = await market.stats.capAndAvailable(false, USE_API) + return { + cap: +capAndAvailable.cap, + available: +capAndAvailable.available, + } + }, refetchInterval: '1m', validationSuite: marketIdValidationSuite, }) -/** - * The purpose of this query is to allow fetching market max leverage on chain - * in order to display the most current data when a wallet is connected. - * */ -const _getMarketMaxLeverage = async ({ marketId }: MarketQuery): Promise => { - const market = getLendMarket(marketId) - const maxLeverage = market.leverage.hasLeverage() ? await market.leverage.maxLeverage(market?.minBands) : null - return { maxLeverage } -} - export const { useQuery: useMarketMaxLeverage } = queryFactory({ queryKey: (params: MarketParams) => [...rootKeys.market(params), 'marketMaxLeverage', 'v1'] as const, - queryFn: _getMarketMaxLeverage, + queryFn: async ({ marketId }: MarketQuery) => { + const market = getLendMarket(marketId) + const maxLeverage = market.leverage.hasLeverage() ? await market.leverage.maxLeverage(market?.minBands) : null + return { maxLeverage } + }, validationSuite: marketIdValidationSuite, }) -/** - * The purpose of this query is to allow fetching market collateral amounts on chain - * in order to display the most current data when a wallet is connected. - * */ -const _getMarketCollateralAmounts = async ({ marketId }: MarketQuery): Promise => { - const market = getLendMarket(marketId) - const ammBalance = await market.stats.ammBalances(false, USE_API) - return { - collateralAmount: +ammBalance.collateral, - borrowedAmount: +ammBalance.borrowed, - } -} - export const { useQuery: useMarketCollateralAmounts, invalidate: invalidateMarketCollateralAmounts } = queryFactory({ queryKey: (params: MarketParams) => [...rootKeys.market(params), 'marketCollateralAmounts', 'v1'] as const, - queryFn: _getMarketCollateralAmounts, + queryFn: async ({ marketId }: MarketQuery) => { + const market = getLendMarket(marketId) + const ammBalance = await market.stats.ammBalances(false, USE_API) + return { + collateralAmount: +ammBalance.collateral, + borrowedAmount: +ammBalance.borrowed, + } + }, refetchInterval: '1m', validationSuite: marketIdValidationSuite, }) -const _fetchOnChainMarketRate = async ({ marketId }: MarketQuery) => ({ - rates: await getLendMarket(marketId).stats.rates(false, false), - rewardsApr: await getLendMarket(marketId).vault.rewardsApr(false), - crvRates: await getLendMarket(marketId).vault.crvApr(false), -}) - /** * The purpose of this query is to allow fetching market parameters on chain * in order to display the most current data when a wallet is connected. @@ -89,22 +53,21 @@ const _fetchOnChainMarketRate = async ({ marketId }: MarketQuery) => ({ * */ export const { useQuery: useMarketOnChainRates, invalidate: invalidateMarketOnChainRates } = queryFactory({ queryKey: (params: MarketParams) => [...rootKeys.market(params), 'marketOnchainData', 'v1'] as const, - queryFn: _fetchOnChainMarketRate, + queryFn: async ({ marketId }: MarketQuery) => ({ + rates: await getLendMarket(marketId).stats.rates(false, false), + rewardsApr: await getLendMarket(marketId).vault.rewardsApr(false), + crvRates: await getLendMarket(marketId).vault.crvApr(false), + }), refetchInterval: '1m', validationSuite: marketIdValidationSuite, }) -const _fetchMarketPricePerShare = async ({ marketId }: MarketQuery) => { - const market = getLendMarket(marketId) - return await market.vault.previewRedeem(1) -} - -/** - * Fetches the price per share of a market on chain - */ export const { useQuery: useMarketPricePerShare, invalidate: invalidateMarketPricePerShare } = queryFactory({ queryKey: (params: MarketParams) => [...rootKeys.market(params), 'marketPricePerShare', 'v1'] as const, - queryFn: _fetchMarketPricePerShare, + queryFn: async ({ marketId }: MarketQuery) => { + const market = getLendMarket(marketId) + return await market.vault.previewRedeem(1) + }, refetchInterval: '1m', validationSuite: marketIdValidationSuite, }) diff --git a/apps/main/src/lend/hooks/useVaultShares.ts b/apps/main/src/lend/hooks/useVaultShares.ts index 177ae84c46..7f70242d89 100644 --- a/apps/main/src/lend/hooks/useVaultShares.ts +++ b/apps/main/src/lend/hooks/useVaultShares.ts @@ -1,9 +1,9 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import { useOneWayMarket } from '@/lend/entities/chain' -import useStore from '@/lend/store/useStore' import { ChainId } from '@/lend/types/lend.types' import { useTokenUsdRate } from '@ui-kit/lib/model/entities/token-usd-rate' import { formatNumber } from '@ui-kit/utils' +import { useMarketPricePerShare } from '../entities/market-details' function formatNumberWithPrecision(value: number, precisionDigits: number) { const valueDigits = Math.max(0, Math.floor(Math.log10(value))) @@ -13,15 +13,15 @@ function formatNumberWithPrecision(value: number, precisionDigits: number) { function useVaultShares(rChainId: ChainId, rOwmId: string, vaultShares: string | number | undefined = '0') { const market = useOneWayMarket(rChainId, rOwmId).data - const pricePerShareResp = useStore((state) => state.markets.vaultPricePerShare[rChainId]?.[rOwmId]) + const { data: pricePerShare, error: errorPricePerShare } = useMarketPricePerShare({ + chainId: rChainId, + marketId: rOwmId, + }) const { address = '', symbol = '' } = market?.borrowed_token ?? {} const { data: usdRate } = useTokenUsdRate({ chainId: rChainId, tokenAddress: address }) - const fetchVaultPricePerShare = useStore((state) => state.markets.fetchVaultPricePerShare) const { borrowedAmount, borrowedAmountUsd } = useMemo<{ borrowedAmount: string; borrowedAmountUsd: string }>(() => { - const { pricePerShare, error } = pricePerShareResp ?? {} - - if (error || usdRate == null || isNaN(usdRate)) { + if (pricePerShare == null || errorPricePerShare || usdRate == null || isNaN(usdRate)) { return { borrowedAmount: '?', borrowedAmountUsd: '?' } } @@ -36,12 +36,7 @@ function useVaultShares(rChainId: ChainId, rOwmId: string, vaultShares: string | } return { borrowedAmount: '', borrowedAmountUsd: '' } - }, [pricePerShareResp, usdRate, symbol, vaultShares]) - - useEffect(() => { - if (market && +vaultShares > 0) void fetchVaultPricePerShare(rChainId, market) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [market?.id, vaultShares]) + }, [pricePerShare, errorPricePerShare, usdRate, symbol, vaultShares]) return { isLoading: borrowedAmount === '' && borrowedAmountUsd === '', diff --git a/apps/main/src/lend/lib/apiLending.ts b/apps/main/src/lend/lib/apiLending.ts index f7136df21a..5054a2d9ee 100644 --- a/apps/main/src/lend/lib/apiLending.ts +++ b/apps/main/src/lend/lib/apiLending.ts @@ -18,7 +18,6 @@ import { MarketRewards, MarketStatBands, MarketStatCapAndAvailable, - MarketStatParameters, MaxRecvLeverageResp, ParsedBandsBalances, Provider, @@ -54,22 +53,6 @@ export const helpers = { } const market = { - fetchStatsParameters: async (markets: OneWayMarketTemplate[]) => { - log('fetchStatsParameters', markets.length) - const results: { [id: string]: MarketStatParameters } = {} - - await PromisePool.for(markets) - .handleError((errorObj, market) => { - const error = getErrorMessage(errorObj, 'error-api') - results[market.id] = { parameters: null, error } - }) - .process(async (market) => { - const parameters = await market.stats.parameters() - results[market.id] = { parameters, error: '' } - }) - - return results - }, fetchStatsBands: async (markets: OneWayMarketTemplate[]) => { log('fetchStatsBands', markets.length) const results: { [id: string]: MarketStatBands } = {} diff --git a/apps/main/src/lend/store/createMarketsSlice.ts b/apps/main/src/lend/store/createMarketsSlice.ts index 32bfedf357..a2729115ad 100644 --- a/apps/main/src/lend/store/createMarketsSlice.ts +++ b/apps/main/src/lend/store/createMarketsSlice.ts @@ -4,7 +4,6 @@ import apiLending from '@/lend/lib/apiLending' import type { State } from '@/lend/store/useStore' import { Api, - ChainId, MarketDetailsView, MarketsMaxLeverageMapper, MarketsPricesMapper, @@ -12,15 +11,12 @@ import { MarketsRewardsMapper, MarketsStatsBandsMapper, MarketsStatsCapAndAvailableMapper, - MarketsStatsParametersMapper, OneWayMarketTemplate, } from '@/lend/types/lend.types' -import { getErrorMessage } from '@/lend/utils/helpers' type StateKey = keyof typeof DEFAULT_STATE type SliceState = { - statsParametersMapper: { [chainId: string]: MarketsStatsParametersMapper } statsBandsMapper: { [chainId: string]: MarketsStatsBandsMapper } statsCapAndAvailableMapper: { [chainId: string]: MarketsStatsCapAndAvailableMapper } maxLeverageMapper: { [chainId: string]: MarketsMaxLeverageMapper } @@ -28,7 +24,6 @@ type SliceState = { ratesMapper: { [chainId: string]: MarketsRatesMapper } rewardsMapper: { [chainId: string]: MarketsRewardsMapper } marketDetailsView: MarketDetailsView - vaultPricePerShare: { [chainId: string]: { [owmId: string]: { pricePerShare: string; error: string } } } } const sliceKey = 'markets' @@ -41,7 +36,6 @@ export type MarketsSlice = { // individual fetchAll(api: Api, OneWayMarketTemplate: OneWayMarketTemplate, shouldRefetch?: boolean): Promise - fetchVaultPricePerShare(chainId: ChainId, OneWayMarketTemplate: OneWayMarketTemplate, shouldRefetch?: boolean): Promise setStateByActiveKey(key: StateKey, activeKey: string, value: T): void setStateByKey(key: StateKey, value: T): void @@ -51,7 +45,6 @@ export type MarketsSlice = { } const DEFAULT_STATE: SliceState = { - statsParametersMapper: {}, statsBandsMapper: {}, statsCapAndAvailableMapper: {}, maxLeverageMapper: {}, @@ -59,7 +52,6 @@ const DEFAULT_STATE: SliceState = { ratesMapper: {}, rewardsMapper: {}, marketDetailsView: '', - vaultPricePerShare: {}, } const createMarketsSlice = (set: StoreApi['setState'], get: StoreApi['getState']): MarketsSlice => ({ @@ -71,7 +63,6 @@ const createMarketsSlice = (set: StoreApi['setState'], get: StoreApi['setState'], get: StoreApi['setState'], get: StoreApi sliceState.fetchDatas(key, api, [OneWayMarketTemplate], shouldRefetch))) }, - fetchVaultPricePerShare: async (chainId, owm, shouldRefetch) => { - const sliceState = get()[sliceKey] - const resp = { pricePerShare: '', error: '' } - - const { pricePerShare: foundPricePerShare } = sliceState.vaultPricePerShare[chainId]?.[owm.id] ?? {} - if (foundPricePerShare && +foundPricePerShare > 0 && !shouldRefetch) { - resp.pricePerShare = foundPricePerShare - } else { - try { - resp.pricePerShare = await owm.vault.previewRedeem(1) - } catch (error) { - console.error(error) - resp.error = getErrorMessage(error, 'error-api') - } - - sliceState.setStateByActiveKey('vaultPricePerShare', chainId.toString(), { - ...(get()[sliceKey].vaultPricePerShare[chainId] ?? {}), - [owm.id]: resp, - }) - } - }, // slice helpers setStateByActiveKey: (key: StateKey, activeKey: string, value: T) => { diff --git a/apps/main/src/lend/types/lend.types.ts b/apps/main/src/lend/types/lend.types.ts index b9ab38c285..89fb35db74 100644 --- a/apps/main/src/lend/types/lend.types.ts +++ b/apps/main/src/lend/types/lend.types.ts @@ -99,18 +99,6 @@ export type LiqRange = { prices: string[] bands: [number, number] } -export type MarketStatParameters = { - parameters: { - fee: string - admin_fee: string - liquidation_discount: string - loan_discount: string - base_price: string - A: string - } | null - error: string -} -export type MarketsStatsParametersMapper = { [owmId: string]: MarketStatParameters } export type BandsBalances = { [band: number]: { borrowed: string; collateral: string } } export type BandsBalancesArr = { borrowed: string; collateral: string; band: number }[] export type ParsedBandsBalances = { diff --git a/apps/main/src/llamalend/features/market-parameters/MarketLoanParameters.tsx b/apps/main/src/llamalend/features/market-parameters/MarketLoanParameters.tsx new file mode 100644 index 0000000000..5a98009fde --- /dev/null +++ b/apps/main/src/llamalend/features/market-parameters/MarketLoanParameters.tsx @@ -0,0 +1,70 @@ +import { useMarketParameters } from '@/llamalend/queries/market-parameters.query' +import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { formatNumber, formatPercent } from '@ui-kit/utils' + +// In [1]: ltv = lambda x: ((x[0] - 1) / x[0])**2 * (1 - x[1]) +// In [2]: ltv((30, 0.11)) +// Out[2]: 0.8316555555555556 +// where x[0] is A, x[1] is loan discount normalised between 0 and 1 (so 11% is 0.11). multiply ltv by 100 to show percentage. +// always show 'max ltv' which is the max possible loan at N=4 (not advisable but hey it exists!). +function getMaxLTV(a: number | undefined, loanDiscount: string | undefined) { + if (a == null || loanDiscount == null) return + return ((+a - 1) / +a) ** 2 * (1 - +loanDiscount / 100) * 100 +} + +export const MarketLoanParameters = ({ chainId, marketId }: { chainId: IChainId; marketId: string }) => { + const { + data: parameters, + isLoading: isLoadingParameters, + error: errorParameters, + } = useMarketParameters({ chainId, marketId }) + + return ( + <> + + + + + + + + + + + + + ) +} diff --git a/apps/main/src/llamalend/features/market-parameters/MarketParameters.tsx b/apps/main/src/llamalend/features/market-parameters/MarketParameters.tsx new file mode 100644 index 0000000000..a34a4ec61a --- /dev/null +++ b/apps/main/src/llamalend/features/market-parameters/MarketParameters.tsx @@ -0,0 +1,71 @@ +// TODO: refactor query into llamalend for both mint and lend markets +// eslint-disable-next-line import/no-restricted-paths +import { useMarketPricePerShare } from '@/lend/entities/market-details' +import { MarketPrices } from '@/llamalend/features/market-parameters/MarketPrices' +import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { Typography } from '@mui/material' +import Stack from '@mui/material/Stack' +import { formatNumber } from '@ui/utils' +import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { MarketLoanParameters } from './MarketLoanParameters' + +const { Spacing } = SizesAndSpaces + +export const MarketParameters = ({ + chainId, + marketId, + marketType, + action, +}: { + chainId: IChainId + marketId: string + marketType: 'lend' | 'mint' + action: 'borrow' | 'supply' +}) => { + const enablePricePerShare = marketType === 'lend' && action === 'supply' + const { + data: pricePerShare, + isLoading: isLoadingPricePerShare, + error: errorPricePerShare, + } = useMarketPricePerShare({ chainId, marketId }, enablePricePerShare) + + return ( + t.design.Layer[2].Fill, padding: Spacing.md, minWidth: '18.75rem' }} + > + {action === 'borrow' && ( + + {t`Loan Parameters`} + + + + + )} + + + {t`Prices`} + + + {enablePricePerShare && ( + + )} + + + + + {t`Market`} + + + + + + ) +} diff --git a/apps/main/src/llamalend/features/market-parameters/MarketPrices.tsx b/apps/main/src/llamalend/features/market-parameters/MarketPrices.tsx new file mode 100644 index 0000000000..82b69a34dc --- /dev/null +++ b/apps/main/src/llamalend/features/market-parameters/MarketPrices.tsx @@ -0,0 +1,40 @@ +import { useMarketBasePrice } from '@/llamalend/queries/market-base-price.query' +import { useMarketOraclePrice } from '@/llamalend/queries/market-oracle-price.query' +import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { formatNumber } from '@ui/utils' +import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' + +export const MarketPrices = ({ chainId, marketId }: { chainId: IChainId; marketId: string }) => { + const { + data: basePrice, + isLoading: isLoadingBasePrice, + error: errorBasePrice, + } = useMarketBasePrice({ chainId, marketId }) + + const { + data: oraclePrice, + isLoading: isLoadingOraclePrice, + error: errorOraclePrice, + } = useMarketOraclePrice({ chainId, marketId }) + + return ( + <> + + + + + ) +} diff --git a/apps/main/src/llamalend/queries/market-parameters.query.ts b/apps/main/src/llamalend/queries/market-parameters.query.ts new file mode 100644 index 0000000000..8436b6020d --- /dev/null +++ b/apps/main/src/llamalend/queries/market-parameters.query.ts @@ -0,0 +1,44 @@ +import { MintMarketTemplate } from '@curvefi/llamalend-api/lib/mintMarkets' +import { queryFactory } from '@ui-kit/lib/model/query' +import { marketIdValidationSuite } from '@ui-kit/lib/model/query/market-id-validation' +import { rootKeys } from '@ui-kit/lib/model/query/root-keys' +import type { MarketQuery, MarketParams } from '@ui-kit/lib/model/query/root-keys' +import { decimal } from '@ui-kit/utils' +import { getLlamaMarket } from '../llama.utils' + +export const { useQuery: useMarketParameters } = queryFactory({ + queryKey: (params: MarketParams) => [...rootKeys.market(params), 'market-parameters'] as const, + queryFn: async ({ marketId }: MarketQuery) => { + const market = getLlamaMarket(marketId) + + // Mint markets don't have their A value in the parameters + // object, but we want it regardless for type similarity + // with lend markets. + if (market instanceof MintMarketTemplate) { + const parameters = await market.stats.parameters() + + return { + fee: decimal(parameters.fee), + admin_fee: decimal(parameters.admin_fee), + rate: decimal(parameters.rate), + future_rate: decimal(parameters.future_rate), + liquidation_discount: decimal(parameters.liquidation_discount), + loan_discount: decimal(parameters.loan_discount), + A: market.A, + } + } else { + const parameters = await market.stats.parameters() + + return { + fee: decimal(parameters.fee), + admin_fee: decimal(parameters.admin_fee), + liquidation_discount: decimal(parameters.liquidation_discount), + loan_discount: decimal(parameters.loan_discount), + base_price: decimal(parameters.base_price), + A: Number(await market.A()), + } + } + }, + refetchInterval: '1m', + validationSuite: marketIdValidationSuite, +}) diff --git a/apps/main/src/loan/components/LoanInfoLlamma/LoanInfoParameters.tsx b/apps/main/src/loan/components/LoanInfoLlamma/LoanInfoParameters.tsx deleted file mode 100644 index ec65422860..0000000000 --- a/apps/main/src/loan/components/LoanInfoLlamma/LoanInfoParameters.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { PageLoanManageProps } from '@/loan/components/PageLoanManage/types' -import useStore from '@/loan/store/useStore' -import Box from '@ui/Box' -import DetailInfo from '@ui/DetailInfo' -import { Chip } from '@ui/Typography' -import { FORMAT_OPTIONS, formatNumber } from '@ui/utils' -import { t } from '@ui-kit/lib/i18n' - -interface Props extends Pick {} - -const LoanInfoParameters = ({ llamma, llammaId }: Props) => { - const loanDetails = useStore((state) => state.loans.detailsMapper[llammaId]) - - const { parameters, priceInfo } = loanDetails ?? {} - - return ( - - - {formatNumber(llamma?.A, { useGrouping: false })} - - - {typeof loanDetails?.basePrice !== 'undefined' && ( - - {formatNumber(loanDetails.basePrice)} - - )} - - - {typeof priceInfo?.oraclePrice !== 'undefined' && ( - - {formatNumber(priceInfo.oraclePrice)} - - )} - - - {typeof parameters?.rate !== 'undefined' && ( - - {formatNumber(parameters.rate, FORMAT_OPTIONS.PERCENT)} - - )} - - - ) -} - -export default LoanInfoParameters diff --git a/apps/main/src/loan/components/LoanInfoLlamma/styles.ts b/apps/main/src/loan/components/LoanInfoLlamma/styles.ts index b0a6edb6ce..a50cea2cdf 100644 --- a/apps/main/src/loan/components/LoanInfoLlamma/styles.ts +++ b/apps/main/src/loan/components/LoanInfoLlamma/styles.ts @@ -2,11 +2,6 @@ import { styled } from 'styled-components' import Box from '@ui/Box' import { breakpoints } from '@ui/utils' -export const SubTitle = styled.h3` - font-size: var(--font-size-3); - margin-bottom: 0.75rem; // 12px -` - export type StatsProps = { isBorderBottom?: boolean isMultiLine?: boolean diff --git a/apps/main/src/loan/components/MarketInformationComp.tsx b/apps/main/src/loan/components/MarketInformationComp.tsx index 41a2ce8817..c1621a22d1 100644 --- a/apps/main/src/loan/components/MarketInformationComp.tsx +++ b/apps/main/src/loan/components/MarketInformationComp.tsx @@ -1,13 +1,14 @@ import { BandsChart } from '@/llamalend/features/bands-chart/BandsChart' import { useBandsData } from '@/llamalend/features/bands-chart/hooks/useBandsData' import { getBandsChartToken } from '@/llamalend/features/bands-chart/utils' +import { MarketParameters } from '@/llamalend/features/market-parameters/MarketParameters' import { BandsComp } from '@/loan/components/BandsComp' import ChartOhlcWrapper from '@/loan/components/ChartOhlcWrapper' import DetailInfoAddressLookup from '@/loan/components/LoanInfoLlamma/components/DetailInfoAddressLookup' -import LoanInfoParameters from '@/loan/components/LoanInfoLlamma/LoanInfoParameters' -import { SubTitle } from '@/loan/components/LoanInfoLlamma/styles' import type { ChainId, Llamma } from '@/loan/types/loan.types' -import { Stack, useTheme } from '@mui/material' +import { useTheme } from '@mui/material' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' import { useConnection } from '@ui-kit/features/connect-wallet' import { useUserProfileStore } from '@ui-kit/features/user-profile' import { useNewBandsChart } from '@ui-kit/hooks/useFeatureFlags' @@ -101,8 +102,9 @@ export const MarketInformationComp = ({ }, }} > - - {t`Contracts`} + + {t`Contracts`} + - t.design.Layer[2].Fill, padding: Spacing.md, minWidth: '18.75rem' }}> - {t`Loan Parameters`} - - + + )} diff --git a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx index 9be9538cfd..0ecb0146cc 100644 --- a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx +++ b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx @@ -1,10 +1,10 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from 'react' import { styled } from 'styled-components' import { DEFAULT_HEALTH_MODE } from '@/llamalend/constants' +import { MarketParameters } from '@/llamalend/features/market-parameters/MarketParameters' import AlertFormError from '@/loan/components/AlertFormError' import DialogHealthWarning from '@/loan/components/DialogHealthWarning' import LoanFormConnect from '@/loan/components/LoanFormConnect' -import LoanInfoParameters from '@/loan/components/LoanInfoLlamma/LoanInfoParameters' import DetailInfo from '@/loan/components/PageLoanCreate/LoanFormCreate/components/DetailInfo' import DialogHealthLeverageWarning from '@/loan/components/PageLoanCreate/LoanFormCreate/components/DialogHealthLeverageWarning' import type { FormStatus, FormValues, PageLoanCreateProps, StepKey } from '@/loan/components/PageLoanCreate/types' @@ -469,7 +469,7 @@ const LoanCreate = ({ {!isAdvancedMode && ( - + )} diff --git a/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx b/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx index a8ea60ba84..dc85ca8296 100644 --- a/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx +++ b/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx @@ -168,7 +168,7 @@ const ActionInfo = ({ color={error ? 'error' : (valueColor ?? 'textPrimary')} component="div" > - {loading ? (typeof loading === 'string' ? loading : MOCK_SKELETON) : value} + {loading ? (typeof loading === 'string' ? loading : MOCK_SKELETON) : error ? '' : value} @@ -176,7 +176,7 @@ const ActionInfo = ({ - {error && ( + {error && !loading && (