Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/main/src/lend/components/ChartOhlcWrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { styled } from 'styled-components'
import PoolActivity from '@/lend/components/ChartOhlcWrapper/PoolActivity'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useLendMarket } from '@/lend/entities/lend-markets'
import { useUserLoanDetails } from '@/lend/hooks/useUserLoanDetails'
import useStore from '@/lend/store/useStore'
import AlertBox from '@ui/AlertBox'
Expand All @@ -18,7 +18,7 @@ import { t } from '@ui-kit/lib/i18n'
import { ChartOhlcWrapperProps, LendingMarketTokens } from './types'

const ChartOhlcWrapper = ({ rChainId, userActiveKey, rOwmId, betaBackgroundColor }: ChartOhlcWrapperProps) => {
const market = useOneWayMarket(rChainId, rOwmId).data
const market = useLendMarket({ chainId: rChainId, marketId: rOwmId })
const borrowMoreActiveKey = useStore((state) => state.loanBorrowMore.activeKey)
const loanRepayActiveKey = useStore((state) => state.loanRepay.activeKey)
const loanCollateralAddActiveKey = useStore((state) => state.loanCollateralAdd.activeKey)
Expand Down
4 changes: 2 additions & 2 deletions apps/main/src/lend/components/DetailInfoCrvIncentives.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useMemo } from 'react'
import { styled } from 'styled-components'
import { zeroAddress } from 'viem'
import { useOneWayMarket } from '@/lend/entities/chain'
import useAbiTotalSupply from '@/lend/hooks/useAbiTotalSupply'
import useSupplyTotalApr from '@/lend/hooks/useSupplyTotalApr'
import { ChainId } from '@/lend/types/lend.types'
Expand All @@ -10,6 +9,7 @@ import Icon from '@ui/Icon'
import TooltipIcon from '@ui/Tooltip/TooltipIcon'
import { FORMAT_OPTIONS, formatNumber } from '@ui/utils'
import { t } from '@ui-kit/lib/i18n'
import { useLendMarket } from '../entities/lend-markets'

type Data = {
label: string
Expand All @@ -30,7 +30,7 @@ const DetailInfoCrvIncentives = ({
lpTokenAmount: string
}) => {
const { tooltipValues } = useSupplyTotalApr(rChainId, rOwmId)
const gaugeAddress = useOneWayMarket(rChainId, rOwmId).data?.addresses?.gauge
const gaugeAddress = useLendMarket({ chainId: rChainId, marketId: rOwmId })?.addresses?.gauge
const gaugeTotalSupply = useAbiTotalSupply(rChainId, gaugeAddress)
const isGaugeAddressInvalid = gaugeAddress === zeroAddress

Expand Down
4 changes: 2 additions & 2 deletions apps/main/src/lend/components/DetailInfoHealth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'
import { styled } from 'styled-components'
import ExternalLink from 'ui/src/Link/ExternalLink'
import { DEFAULT_HEALTH_MODE } from '@/lend/components/PageLoanManage/utils'
import { useOneWayMarket } from '@/lend/entities/chain'
import useStore from '@/lend/store/useStore'
import { HealthColorKey, HealthMode, PageContentProps } from '@/lend/types/lend.types'
import { getHealthMode } from '@/lend/utils/health.util'
Expand All @@ -12,6 +11,7 @@ import Icon from '@ui/Icon'
import IconTooltip from '@ui/Tooltip/TooltipIcon'
import { formatNumber } from '@ui/utils'
import { t } from '@ui-kit/lib/i18n'
import { useLendMarket } from '../entities/lend-markets'
import { useUserLoanDetails } from '../hooks/useUserLoanDetails'

type FormType = 'create-loan' | 'collateral-decrease' | ''
Expand Down Expand Up @@ -44,7 +44,7 @@ const DetailInfoHealth = ({
loading: boolean
setHealthMode: Dispatch<SetStateAction<HealthMode>>
}) => {
const market = useOneWayMarket(rChainId, rOwmId).data
const market = useLendMarket({ chainId: rChainId, marketId: rOwmId })
const oraclePriceBand = useStore((state) => state.markets.pricesMapper[rChainId]?.[rOwmId]?.prices?.oraclePriceBand)
const {
healthFull: healthFullCurrent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fragment, useEffect } from 'react'
import { SubTitle } from '@/lend/components/DetailsMarket/styles'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useLendMarket } from '@/lend/entities/lend-markets'
import useStore from '@/lend/store/useStore'
import { ChainId } from '@/lend/types/lend.types'
import Box from '@ui/Box'
Expand All @@ -20,7 +20,7 @@ const MarketParameters = ({
rOwmId: string
type: 'borrow' | 'supply'
}) => {
const owm = useOneWayMarket(rChainId, rOwmId).data
const owm = useLendMarket({ chainId: rChainId, marketId: rOwmId })
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])
Expand Down
8 changes: 4 additions & 4 deletions apps/main/src/lend/components/PageLoanCreate/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ChartOhlcWrapper from '@/lend/components/ChartOhlcWrapper'
import { MarketInformationComp } from '@/lend/components/MarketInformationComp'
import { MarketInformationTabs } from '@/lend/components/MarketInformationTabs'
import LoanCreate from '@/lend/components/PageLoanCreate/index'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useLendMarket } from '@/lend/entities/lend-markets'
import { useMarketDetails } from '@/lend/hooks/useMarketDetails'
import useTitleMapper from '@/lend/hooks/useTitleMapper'
import { helpers } from '@/lend/lib/apiLending'
Expand Down Expand Up @@ -38,7 +38,7 @@ const Page = () => {
const params = useParams<MarketUrlParams>()
const { rMarket, rChainId, rFormType } = parseMarketParams(params)

const { data: market, isSuccess } = useOneWayMarket(rChainId, rMarket)
const market = useLendMarket({ chainId: rChainId, marketId: rMarket })
const { llamaApi: api = null, connectState } = useConnection()
const titleMapper = useTitleMapper()
const { provider, connect } = useWallet()
Expand All @@ -61,11 +61,11 @@ const Page = () => {
})

useEffect(() => {
if (isSuccess && !market) {
if (api?.hydrated && !market) {
console.warn(`Market ${rMarket} not found. Redirecting to market list.`)
push(getCollateralListPathname(params))
}
}, [isSuccess, market, params, push, rMarket])
}, [api?.hydrated, market, params, push, rMarket])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this effect isn't triggered properly 😿
if you visit a market that doesn't exist, the hydration finishes but the redirect takes an unspecified amount of time to happen (when the page is re-rendered for some other reason)


useEffect(() => {
// delay fetch rest after form details are fetched first
Expand Down
4 changes: 2 additions & 2 deletions apps/main/src/lend/components/PageLoanManage/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MarketInformationComp } from '@/lend/components/MarketInformationComp'
import { MarketInformationTabs } from '@/lend/components/MarketInformationTabs'
import LoanMange from '@/lend/components/PageLoanManage/index'
import type { DetailInfoTypes } from '@/lend/components/PageLoanManage/types'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useLendMarket } from '@/lend/entities/lend-markets'
import { useBorrowPositionDetails } from '@/lend/hooks/useBorrowPositionDetails'
import { useMarketDetails } from '@/lend/hooks/useMarketDetails'
import useTitleMapper from '@/lend/hooks/useTitleMapper'
Expand Down Expand Up @@ -41,7 +41,7 @@ const Page = () => {
const { rMarket, rChainId, rFormType } = parseMarketParams(params)
const { llamaApi: api = null, connectState } = useConnection()
const titleMapper = useTitleMapper()
const market = useOneWayMarket(rChainId, rMarket).data
const market = useLendMarket({ chainId: rChainId, marketId: rMarket })
const rOwmId = market?.id ?? ''
const userActiveKey = helpers.getUserActiveKey(api, market!)
const isMdUp = useLayoutStore((state) => state.isMdUp)
Expand Down
4 changes: 2 additions & 2 deletions apps/main/src/lend/components/PageVault/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MarketInformationComp } from '@/lend/components/MarketInformationComp'
import { MarketInformationTabs } from '@/lend/components/MarketInformationTabs'
import type { DetailInfoTypes } from '@/lend/components/PageLoanManage/types'
import Vault from '@/lend/components/PageVault/index'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useLendMarket } from '@/lend/entities/lend-markets'
import { useMarketDetails } from '@/lend/hooks/useMarketDetails'
import { useSupplyPositionDetails } from '@/lend/hooks/useSupplyPositionDetails'
import useTitleMapper from '@/lend/hooks/useTitleMapper'
Expand Down Expand Up @@ -42,7 +42,7 @@ const Page = () => {
const { connect, provider } = useWallet()
const { llamaApi: api = null, connectState } = useConnection()
const titleMapper = useTitleMapper()
const market = useOneWayMarket(rChainId, rMarket).data
const market = useLendMarket({ chainId: rChainId, marketId: rMarket })

const isPageVisible = useLayoutStore((state) => state.isPageVisible)
const fetchAllMarketDetails = useStore((state) => state.markets.fetchAll)
Expand Down
38 changes: 0 additions & 38 deletions apps/main/src/lend/entities/chain/chain-hooks.ts

This file was deleted.

17 changes: 0 additions & 17 deletions apps/main/src/lend/entities/chain/chain-query.ts

This file was deleted.

34 changes: 16 additions & 18 deletions apps/main/src/lend/entities/chain/chain-tvl.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
import { useMemo } from 'react'
import useStore from '@/lend/store/useStore'
import { ChainId } from '@/lend/types/lend.types'
import { useConnection } from '@ui-kit/features/connect-wallet'
import { useTokenUsdRates } from '@ui-kit/lib/model/entities/token-usd-rate'
import { FETCHING, PartialQueryResult, READY } from '@ui-kit/lib/queries'
import { useOneWayMarketMapping } from './chain-hooks'
import { useLendMarketMapping } from '../lend-markets'
import { calculateChainTvl } from './tvl'

/** Todo: we should replace this entire hook with prices API some day, there should be an endpoint available */
export const useTvl = (chainId: ChainId | undefined): PartialQueryResult<number> => {
const marketMapping = useOneWayMarketMapping({ chainId }).data
const { llamaApi: api } = useConnection()

const marketMapping = useLendMarketMapping({ chainId })
const markets = useMemo(
() => (api && marketMapping ? Object.values(marketMapping).map(api.getLendMarket) : []),
[api, marketMapping],
)
const marketsCollateralMapper = useStore((state) => chainId && state.markets.statsAmmBalancesMapper[chainId])
const marketsTotalSupplyMapper = useStore((state) => chainId && state.markets.totalLiquidityMapper[chainId])
const marketsTotalDebtMapper = useStore((state) => chainId && state.markets.statsTotalsMapper[chainId])
const tokenAddresses = useMemo(
() =>
Object.values(marketMapping ?? {})
// note: include the borrowed tokens here, to be used in `filterSmallMarkets`
.flatMap((market) => [market.borrowed_token, market.collateral_token])
.map((t) => t.address),
[marketMapping],
() => markets.flatMap((market) => [market.borrowed_token, market.collateral_token]).map((t) => t.address),
[markets],
)
const { data: tokenUsdRates, isError: isUsdRatesError } = useTokenUsdRates({ chainId, tokenAddresses })

return useMemo(() => {
if (
!marketMapping ||
!marketsCollateralMapper ||
!marketsTotalSupplyMapper ||
!marketsTotalDebtMapper ||
!tokenUsdRates
) {
if (!marketsCollateralMapper || !marketsTotalSupplyMapper || !marketsTotalDebtMapper || !tokenUsdRates) {
return { ...FETCHING, isError: isUsdRatesError }
}
const data = calculateChainTvl(
marketMapping,
markets,
marketsCollateralMapper,
tokenUsdRates,
marketsTotalDebtMapper,
marketsTotalSupplyMapper,
)
return { ...READY, data }
}, [
isUsdRatesError,
marketMapping,
marketsCollateralMapper,
marketsTotalSupplyMapper,
marketsTotalDebtMapper,
tokenUsdRates,
markets,
isUsdRatesError,
])
}
1 change: 0 additions & 1 deletion apps/main/src/lend/entities/chain/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { useChainId } from './chain-info'
export { useTvl } from './chain-tvl'
export { useOneWayMarket, useOneWayMarketMapping } from './chain-hooks'
export { calculateChainTvl } from './tvl'
20 changes: 9 additions & 11 deletions apps/main/src/lend/entities/chain/tvl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IDict } from '@curvefi/llamalend-api/lib/interfaces'
import { logSuccess } from '@ui-kit/lib'

export function calculateChainTvl(
marketMapping: IDict<OneWayMarketTemplate>,
markets: OneWayMarketTemplate[],
marketsCollateralMapper: MarketsStatsAMMBalancesMapper,
tokenUsdRates: IDict<number>,
marketsTotalDebtMapper: MarketsStatsTotalsMapper,
Expand All @@ -18,17 +18,15 @@ export function calculateChainTvl(
let totalLiquidity = 0
let totalDebt = 0

Object.entries(marketMapping)
.filter(([key]) => !key.startsWith('0x')) // the market mapping has addresses and ids, we only want the ids
.forEach(([id, { collateral_token }]) => {
const ammBalance = marketsCollateralMapper[id] ?? {}
const collateralUsdRate = tokenUsdRates[collateral_token.address] ?? 0
const marketTotalCollateralUsd = +(ammBalance?.collateral ?? '0') * collateralUsdRate
markets.forEach(({ id, collateral_token }) => {
const ammBalance = marketsCollateralMapper[id] ?? {}
const collateralUsdRate = tokenUsdRates[collateral_token.address] ?? 0
const marketTotalCollateralUsd = +(ammBalance?.collateral ?? '0') * collateralUsdRate

totalCollateral += marketTotalCollateralUsd
totalDebt += +marketsTotalDebtMapper[id]?.totalDebt
totalLiquidity += +marketsTotalSupplyMapper[id]?.totalLiquidity
})
totalCollateral += marketTotalCollateralUsd
totalDebt += +marketsTotalDebtMapper[id]?.totalDebt
totalLiquidity += +marketsTotalSupplyMapper[id]?.totalLiquidity
})

const tvl = totalCollateral + totalLiquidity - totalDebt
logSuccess(['chain-tvl'], { totalCollateral, totalLiquidity, totalDebt }, tvl)
Expand Down
63 changes: 63 additions & 0 deletions apps/main/src/lend/entities/lend-markets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useMemo } from 'react'
import { isAddress } from 'viem'
import networks from '@/lend/networks'
import { ChainId } from '@/lend/types/lend.types'
import { fromEntries } from '@curvefi/prices-api/objects.util'
import { useConnection } from '@ui-kit/features/connect-wallet'
import type { Address } from '@ui-kit/utils'

/**
* Hook to get a mapping of lending market controller address to market name for all lending markets on a specific chain.
* Primarily useful fetching lending markets via URL.
*/
export const useLendMarketMapping = ({ chainId }: { chainId: ChainId | undefined }) => {
const { llamaApi: api } = useConnection()
const mapping = useMemo(
() =>
api?.hydrated &&
chainId &&
fromEntries(
api.lendMarkets
.getMarketList()
.filter((marketName) => !networks[chainId].hideMarketsInUI[marketName])
.map((name) => [api.getLendMarket(name).addresses.controller as Address, name]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason we created the mapping like this was because we wanted to use a query.
If this is a normal hook, there is no reason to return the name only, we might as well return the class instance

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a query, though? We only need the mapping for redirecting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not reliably add the hydration validation in the query, reactivity for api.hydrated is wonky. But a hook with useMemo and api?.hydrated seemed to work, although you said it's still wonky in some way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we need to properly use the state machine from React. Currently it works only when it re-renders for some other reasons. Changing a field of an object is not supported

),
// Need to specifically watch to api?.hydrated, as simply watching api as a whole won't trigger when hydrated is set to true by `useHydration`
// eslint-disable-next-line react-hooks/exhaustive-deps
[api?.hydrated, chainId],
Comment on lines +26 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please never disable this rule, it's always a bug waiting to happen

Suggested change
// eslint-disable-next-line react-hooks/exhaustive-deps
[api?.hydrated, chainId],
[api, api?.hydrated, chainId],

)

return mapping || undefined
}

/**
* Hook to get a specific lending market by its id or controller address.
* @param chainId The chain for which to get the lending market of
* @param marketId Lend market id or controller address
* @returns The market instance, if found.
*/
export const useLendMarket = ({ chainId, marketId }: { chainId: ChainId; marketId: string | Address }) => {
const marketMapping = useLendMarketMapping({ chainId })
const { llamaApi: api } = useConnection()

return useMemo(() => {
if (!api) return undefined

// If markets aren't found they throw an error, but we want to return undefined instead
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't do this, ignoring errors is bad. Why are we trying to retrieve a market if it's not found?
If the API is hydrated it should never throw an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because somebody might enter gibberish into the URL?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue that should crash the page with an error (or give a proper 404), instead of being ignored which could lead to the page being left in loading state.

const safeGetMarket = (id: string) => {
try {
return api.getLendMarket(id)
} catch {
return undefined
}
}

// Try to get the market by name first
if (!isAddress(marketId)) return safeGetMarket(marketId)

// Try to get by controller address mapping
return marketMapping && marketId in marketMapping && api.hydrated && api.chainId === chainId
? safeGetMarket(marketMapping[marketId])
: undefined
}, [api, chainId, marketId, marketMapping])
}
4 changes: 2 additions & 2 deletions apps/main/src/lend/hooks/useSupplyTotalApr.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useMemo } from 'react'
import { zeroAddress } from 'viem'
import { useOneWayMarket } from '@/lend/entities/chain'
import { useMarketOnChainRates } from '@/lend/entities/market-details'
import useStore from '@/lend/store/useStore'
import { ChainId, MarketRates, RewardOther, MarketRewards } from '@/lend/types/lend.types'
import { getTotalApr } from '@/lend/utils/utilsRewards'
import { FORMAT_OPTIONS, formatNumber } from '@ui/utils'
import { useLendMarket } from '../entities/lend-markets'

function useSupplyTotalApr(rChainId: ChainId, rOwmId: string) {
const market = useOneWayMarket(rChainId, rOwmId).data
const market = useLendMarket({ chainId: rChainId, marketId: rOwmId })
const marketRewardsResp = useStore((state) => state.markets.rewardsMapper[rChainId]?.[rOwmId])
const marketRatesResp = useStore((state) => state.markets.ratesMapper[rChainId]?.[rOwmId])
const {
Expand Down
Loading
Loading