From ccc539d41b22433130ce805efa41bbdb81a35d7d Mon Sep 17 00:00:00 2001 From: Alunara Date: Tue, 23 Sep 2025 17:58:56 +0200 Subject: [PATCH 1/6] refactor: rename useOneWayMarket to useLendMarket and simplify hooks --- .../components/ChartOhlcWrapper/index.tsx | 4 +- .../components/DetailInfoCrvIncentives.tsx | 4 +- .../src/lend/components/DetailInfoHealth.tsx | 4 +- .../components/MarketParameters.tsx | 4 +- .../lend/components/PageLoanCreate/Page.tsx | 4 +- .../lend/components/PageLoanManage/Page.tsx | 4 +- .../src/lend/components/PageVault/Page.tsx | 4 +- .../src/lend/entities/chain/chain-hooks.ts | 38 --------------- .../src/lend/entities/chain/chain-query.ts | 17 ------- .../main/src/lend/entities/chain/chain-tvl.ts | 10 ++-- apps/main/src/lend/entities/chain/index.ts | 1 - apps/main/src/lend/entities/chain/tvl.ts | 20 ++++---- apps/main/src/lend/entities/lend-markets.ts | 48 +++++++++++++++++++ apps/main/src/lend/hooks/useSupplyTotalApr.ts | 4 +- apps/main/src/lend/hooks/useVaultShares.ts | 4 +- apps/main/src/lend/store/createAppSlice.ts | 16 ++----- 16 files changed, 83 insertions(+), 103 deletions(-) delete mode 100644 apps/main/src/lend/entities/chain/chain-hooks.ts delete mode 100644 apps/main/src/lend/entities/chain/chain-query.ts create mode 100644 apps/main/src/lend/entities/lend-markets.ts diff --git a/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx b/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx index a2225b848e..9df32f089a 100644 --- a/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx +++ b/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx @@ -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' @@ -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 { data: 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) diff --git a/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx b/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx index 95478d63d4..0b06c079ca 100644 --- a/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx +++ b/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx @@ -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' @@ -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 @@ -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 }).data?.addresses?.gauge const gaugeTotalSupply = useAbiTotalSupply(rChainId, gaugeAddress) const isGaugeAddressInvalid = gaugeAddress === zeroAddress diff --git a/apps/main/src/lend/components/DetailInfoHealth.tsx b/apps/main/src/lend/components/DetailInfoHealth.tsx index 5d87de4ce0..804b0f209f 100644 --- a/apps/main/src/lend/components/DetailInfoHealth.tsx +++ b/apps/main/src/lend/components/DetailInfoHealth.tsx @@ -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' @@ -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' | '' @@ -44,7 +44,7 @@ const DetailInfoHealth = ({ loading: boolean setHealthMode: Dispatch> }) => { - const market = useOneWayMarket(rChainId, rOwmId).data + const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) const oraclePriceBand = useStore((state) => state.markets.pricesMapper[rChainId]?.[rOwmId]?.prices?.oraclePriceBand) const { healthFull: healthFullCurrent, diff --git a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx b/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx index 2c63762164..73aca56b42 100644 --- a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx +++ b/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx @@ -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' @@ -20,7 +20,7 @@ const MarketParameters = ({ rOwmId: string type: 'borrow' | 'supply' }) => { - const owm = useOneWayMarket(rChainId, rOwmId).data + const { data: 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]) diff --git a/apps/main/src/lend/components/PageLoanCreate/Page.tsx b/apps/main/src/lend/components/PageLoanCreate/Page.tsx index 8b72931473..7c5660fa60 100644 --- a/apps/main/src/lend/components/PageLoanCreate/Page.tsx +++ b/apps/main/src/lend/components/PageLoanCreate/Page.tsx @@ -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' @@ -38,7 +38,7 @@ const Page = () => { const params = useParams() const { rMarket, rChainId, rFormType } = parseMarketParams(params) - const { data: market, isSuccess } = useOneWayMarket(rChainId, rMarket) + const { data: market, isSuccess } = useLendMarket({ chainId: rChainId, marketId: rMarket }) const { llamaApi: api = null, connectState } = useConnection() const titleMapper = useTitleMapper() const { provider, connect } = useWallet() diff --git a/apps/main/src/lend/components/PageLoanManage/Page.tsx b/apps/main/src/lend/components/PageLoanManage/Page.tsx index a7df530e18..0dc9dbf90a 100644 --- a/apps/main/src/lend/components/PageLoanManage/Page.tsx +++ b/apps/main/src/lend/components/PageLoanManage/Page.tsx @@ -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' @@ -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 { data: market } = useLendMarket({ chainId: rChainId, marketId: rMarket }) const rOwmId = market?.id ?? '' const userActiveKey = helpers.getUserActiveKey(api, market!) const isMdUp = useLayoutStore((state) => state.isMdUp) diff --git a/apps/main/src/lend/components/PageVault/Page.tsx b/apps/main/src/lend/components/PageVault/Page.tsx index 88d4acd734..16a5f5a5bd 100644 --- a/apps/main/src/lend/components/PageVault/Page.tsx +++ b/apps/main/src/lend/components/PageVault/Page.tsx @@ -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' @@ -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 { data: market } = useLendMarket({ chainId: rChainId, marketId: rMarket }) const isPageVisible = useLayoutStore((state) => state.isPageVisible) const fetchAllMarketDetails = useStore((state) => state.markets.fetchAll) diff --git a/apps/main/src/lend/entities/chain/chain-hooks.ts b/apps/main/src/lend/entities/chain/chain-hooks.ts deleted file mode 100644 index 0a19d12c05..0000000000 --- a/apps/main/src/lend/entities/chain/chain-hooks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useMemo } from 'react' -import networks from '@/lend/networks' -import useStore from '@/lend/store/useStore' -import { ChainId, OneWayMarketTemplate } from '@/lend/types/lend.types' -import { useConnection } from '@ui-kit/features/connect-wallet' -import { ChainParams } from '@ui-kit/lib/model/query' -import { useOneWayMarketNames } from './chain-query' - -export const useOneWayMarketMapping = (params: ChainParams) => { - const { chainId } = params - const { data: marketNames, isSuccess, error } = useOneWayMarketNames(params) - const hydratedChainId = useStore((state) => state.hydratedChainId) - const { llamaApi: api } = useConnection() - const apiChainId = api?.chainId - const data: Record | undefined = useMemo( - () => - // note: only during hydration `api` internally retrieves all the markets, and we can call `getOneWayMarket` - marketNames && api && chainId == apiChainId && hydratedChainId === chainId - ? Object.fromEntries( - marketNames - .filter((marketName) => !networks[chainId!].hideMarketsInUI[marketName]) - .map((name) => [name, api.getLendMarket(name)] as const) - .flatMap(([name, market]) => [ - [name, market], - [market.addresses.controller, market], - ]), - ) - : undefined, - [api, apiChainId, chainId, hydratedChainId, marketNames], - ) - return { data, isSuccess, error } -} - -export const useOneWayMarket = (chainId: ChainId, marketName: string) => { - const { data: markets, isSuccess, ...rest } = useOneWayMarketMapping({ chainId }) - const market = markets?.[marketName] - return { data: market, isSuccess: isSuccess && !!markets, ...rest } -} diff --git a/apps/main/src/lend/entities/chain/chain-query.ts b/apps/main/src/lend/entities/chain/chain-query.ts deleted file mode 100644 index 6ad0bb81cf..0000000000 --- a/apps/main/src/lend/entities/chain/chain-query.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ChainId } from '@/lend/types/lend.types' -import { requireLib } from '@ui-kit/features/connect-wallet' -import { ChainParams, ChainQuery, queryFactory } from '@ui-kit/lib/model/query' -import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-validation' - -export const { useQuery: useOneWayMarketNames, prefetchQuery: prefetchMarkets } = queryFactory({ - queryKey: ({ chainId }: ChainParams) => ['chain', { chainId }, 'markets'] as const, - queryFn: async ({ chainId }: ChainQuery): Promise => { - const useAPI = chainId !== 146 // disable API for sonic - const api = requireLib('llamaApi') - await api.lendMarkets.fetchMarkets(useAPI) - return api.lendMarkets.getMarketList() - }, - staleTime: '5m', - refetchInterval: '1m', - validationSuite: llamaApiValidationSuite, -}) diff --git a/apps/main/src/lend/entities/chain/chain-tvl.ts b/apps/main/src/lend/entities/chain/chain-tvl.ts index ef614f3ef7..34678b7d74 100644 --- a/apps/main/src/lend/entities/chain/chain-tvl.ts +++ b/apps/main/src/lend/entities/chain/chain-tvl.ts @@ -3,20 +3,16 @@ import useStore from '@/lend/store/useStore' import { ChainId } from '@/lend/types/lend.types' 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 { useLendMarkets } from '../lend-markets' import { calculateChainTvl } from './tvl' export const useTvl = (chainId: ChainId | undefined): PartialQueryResult => { - const marketMapping = useOneWayMarketMapping({ chainId }).data + const { data: marketMapping = [] } = useLendMarkets({ chainId }) 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.flatMap((market) => [market.borrowed_token, market.collateral_token]).map((t) => t.address), [marketMapping], ) const { data: tokenUsdRates, isError: isUsdRatesError } = useTokenUsdRates({ chainId, tokenAddresses }) diff --git a/apps/main/src/lend/entities/chain/index.ts b/apps/main/src/lend/entities/chain/index.ts index c378a6590c..2b2c31a73a 100644 --- a/apps/main/src/lend/entities/chain/index.ts +++ b/apps/main/src/lend/entities/chain/index.ts @@ -1,4 +1,3 @@ export { useChainId } from './chain-info' export { useTvl } from './chain-tvl' -export { useOneWayMarket, useOneWayMarketMapping } from './chain-hooks' export { calculateChainTvl } from './tvl' diff --git a/apps/main/src/lend/entities/chain/tvl.ts b/apps/main/src/lend/entities/chain/tvl.ts index 9508b14c9b..005f50fb72 100644 --- a/apps/main/src/lend/entities/chain/tvl.ts +++ b/apps/main/src/lend/entities/chain/tvl.ts @@ -8,7 +8,7 @@ import { IDict } from '@curvefi/llamalend-api/lib/interfaces' import { logSuccess } from '@ui-kit/lib' export function calculateChainTvl( - marketMapping: IDict, + markets: OneWayMarketTemplate[], marketsCollateralMapper: MarketsStatsAMMBalancesMapper, tokenUsdRates: IDict, marketsTotalDebtMapper: MarketsStatsTotalsMapper, @@ -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) diff --git a/apps/main/src/lend/entities/lend-markets.ts b/apps/main/src/lend/entities/lend-markets.ts new file mode 100644 index 0000000000..8cca84f485 --- /dev/null +++ b/apps/main/src/lend/entities/lend-markets.ts @@ -0,0 +1,48 @@ +import { useMemo } from 'react' +import networks from '@/lend/networks' +import { ChainId } from '@/lend/types/lend.types' +import { requireLib } from '@ui-kit/features/connect-wallet' +import { ChainParams, ChainQuery, queryFactory } from '@ui-kit/lib/model/query' +import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-validation' + +export const { useQuery: useLendMarkets, prefetchQuery: prefetchLendMarkets } = queryFactory({ + queryKey: ({ chainId }: ChainParams) => ['chain', { chainId }, 'lend-markets'] as const, + queryFn: async ({ chainId }: ChainQuery) => { + const useAPI = chainId !== 146 // disable API for sonic + const api = requireLib('llamaApi') + await api.lendMarkets.fetchMarkets(useAPI) + return api.lendMarkets + .getMarketList() + .filter((marketName) => !networks[chainId].hideMarketsInUI[marketName]) + .map((name) => api.getLendMarket(name)) + }, + staleTime: '5m', + refetchInterval: '1m', + validationSuite: llamaApiValidationSuite, +}) + +/** + * 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 }) => { + const { data: markets, isSuccess, error } = useLendMarkets({ chainId }) + + /** Create mappings from market name or controller id to market instance. */ + const mapping = useMemo( + () => + markets && + Object.fromEntries( + markets.flatMap((market) => [ + [market.name, market], + [market.addresses.controller, market], + ]), + ), + [markets], + ) + + const market = mapping?.[marketId] + return { data: market, isSuccess, error } +} diff --git a/apps/main/src/lend/hooks/useSupplyTotalApr.ts b/apps/main/src/lend/hooks/useSupplyTotalApr.ts index 2da3374f2c..5004ef425d 100644 --- a/apps/main/src/lend/hooks/useSupplyTotalApr.ts +++ b/apps/main/src/lend/hooks/useSupplyTotalApr.ts @@ -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 { data: 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 { diff --git a/apps/main/src/lend/hooks/useVaultShares.ts b/apps/main/src/lend/hooks/useVaultShares.ts index 177ae84c46..8023e22a68 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 { 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 { useLendMarket } from '../entities/lend-markets' function formatNumberWithPrecision(value: number, precisionDigits: number) { const valueDigits = Math.max(0, Math.floor(Math.log10(value))) @@ -12,7 +12,7 @@ function formatNumberWithPrecision(value: number, precisionDigits: number) { } function useVaultShares(rChainId: ChainId, rOwmId: string, vaultShares: string | number | undefined = '0') { - const market = useOneWayMarket(rChainId, rOwmId).data + const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) const pricePerShareResp = useStore((state) => state.markets.vaultPricePerShare[rChainId]?.[rOwmId]) const { address = '', symbol = '' } = market?.borrowed_token ?? {} const { data: usdRate } = useTokenUsdRate({ chainId: rChainId, tokenAddress: address }) diff --git a/apps/main/src/lend/store/createAppSlice.ts b/apps/main/src/lend/store/createAppSlice.ts index 168934c97e..1cccbb934d 100644 --- a/apps/main/src/lend/store/createAppSlice.ts +++ b/apps/main/src/lend/store/createAppSlice.ts @@ -1,18 +1,16 @@ import { produce } from 'immer' import lodash from 'lodash' import type { GetState, SetState } from 'zustand' -import { prefetchMarkets } from '@/lend/entities/chain/chain-query' import type { State } from '@/lend/store/useStore' -import { Api, ChainId, Wallet } from '@/lend/types/lend.types' +import { Api, Wallet } from '@/lend/types/lend.types' import { log } from '@ui-kit/lib/logging' +import { prefetchLendMarkets } from '../entities/lend-markets' export type DefaultStateKeys = keyof typeof DEFAULT_STATE export type SliceKey = keyof State | '' export type StateKey = string -type SliceState = { - hydratedChainId: ChainId | null -} +type SliceState = {} // prettier-ignore export interface AppSlice extends SliceState { @@ -27,9 +25,7 @@ export interface AppSlice extends SliceState { resetAppState(sliceKey: SliceKey, defaultState: T): void } -const DEFAULT_STATE: SliceState = { - hydratedChainId: null, -} +const DEFAULT_STATE: SliceState = {} satisfies SliceState const createAppSlice = (set: SetState, get: GetState): AppSlice => ({ ...DEFAULT_STATE, @@ -41,7 +37,6 @@ const createAppSlice = (set: SetState, get: GetState): AppSlice => ) }, hydrate: async (api, prevApi, wallet) => { - get().updateGlobalStoreByKey('hydratedChainId', null) if (!api) return const isNetworkSwitched = !!prevApi?.chainId && prevApi.chainId !== api.chainId @@ -75,10 +70,9 @@ const createAppSlice = (set: SetState, get: GetState): AppSlice => } // unfortunately, we cannot use markets from the cache as that leaves curve-lending-js in an inconsistent state - await prefetchMarkets({ chainId: api.chainId }) + await prefetchLendMarkets({ chainId: api.chainId }) log('Hydrating Lend - Complete') - get().updateGlobalStoreByKey('hydratedChainId', api.chainId) }, setAppStateByActiveKey: (sliceKey: SliceKey, key: StateKey, activeKey: string, value: T, showLog?: boolean) => { set( From 4b2b827a593dfcc9235da70886e39699a110a0f4 Mon Sep 17 00:00:00 2001 From: Alunara Date: Wed, 24 Sep 2025 17:13:32 +0200 Subject: [PATCH 2/6] feat: add optional beHydrated prop to llamaApiValidationSuite --- .../borrow/queries/borrow-apy.query.ts | 2 +- .../queries/borrow-oracle-price-band.query.ts | 2 +- .../queries/borrow-oracle-price.query.ts | 2 +- .../entities/appstats-total-crvusd-supply.ts | 2 +- .../lib/model/query/curve-api-validation.ts | 23 ++++++++++++++----- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts index 9ccd514014..410609634d 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts @@ -37,6 +37,6 @@ export const { useQuery: useMarketRates } = queryFactory({ ? convertRates(await market.stats.rates()) : convertRates({ borrowApr: (await market.stats.parameters()).rate }) }, - validationSuite: llamaApiValidationSuite, + validationSuite: llamaApiValidationSuite(), dependencies: (params) => [maxBorrowReceiveKey(params)], }) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts index fed9837102..b7ff40f93d 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts @@ -7,5 +7,5 @@ export const { useQuery: useBorrowOraclePriceBand } = queryFactory({ queryKey: ({ chainId, poolId }: PoolParams) => [...rootKeys.pool({ chainId, poolId }), 'oraclePriceBand'] as const, queryFn: ({ poolId }: PoolQuery): Promise => getLlamaMarket(poolId).oraclePriceBand(), - validationSuite: llamaApiValidationSuite, + validationSuite: llamaApiValidationSuite(), }) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts index dc38e406d0..0816df3589 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts @@ -7,5 +7,5 @@ export const { useQuery: useBorrowOraclePrice } = queryFactory({ queryKey: ({ chainId, poolId }: PoolParams) => [...rootKeys.pool({ chainId, poolId }), 'oraclePrice'] as const, queryFn: ({ poolId }: PoolQuery): Promise => getLlamaMarket(poolId).oraclePrice(), - validationSuite: llamaApiValidationSuite, + validationSuite: llamaApiValidationSuite(), }) diff --git a/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts b/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts index 6cbee3460a..dc603d361a 100644 --- a/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts +++ b/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts @@ -8,5 +8,5 @@ import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-valid export const { useQuery: useAppStatsTotalCrvusdSupply } = queryFactory({ queryKey: (params: ChainParams) => ['appStatsTotalCrvusdSupply', { chainId: params.chainId }] as const, queryFn: ({ chainId }: ChainQuery) => networks[chainId].api.helpers.getTotalSupply(requireLib('llamaApi')), - validationSuite: llamaApiValidationSuite, + validationSuite: llamaApiValidationSuite(), }) diff --git a/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts b/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts index 28e45a1dcf..a1b235f082 100644 --- a/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts +++ b/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts @@ -20,18 +20,29 @@ export const curveApiValidationSuite = createValidationSuite( }, ) +type ApiValidationParams = { + /** The API should be fully hydrated. */ + beHydrated?: boolean +} + export const llamaApiValidationGroup = ({ chainId, -}: ChainParams) => + beHydrated, +}: ChainParams & ApiValidationParams) => group('apiValidation', () => { test('api', 'API chain ID mismatch', () => { enforce(getLib('llamaApi')?.chainId).message('Chain ID should be loaded').equals(chainId) }) + + if (beHydrated) { + test('apiHydrated', 'API should be hydrated', () => { + enforce(getLib('llamaApi')?.hydrated).message('API should be hydrated').isTrue() + }) + } }) -export const llamaApiValidationSuite = createValidationSuite( - (params: ChainParams) => { +export const llamaApiValidationSuite = (apiValidationParams: ApiValidationParams = { beHydrated: false }) => + createValidationSuite((params: ChainParams) => { chainValidationGroup(params) - llamaApiValidationGroup(params) - }, -) + llamaApiValidationGroup({ ...params, ...apiValidationParams }) + }) From c17a98a8942d2f045412af111f3529dcb679fb94 Mon Sep 17 00:00:00 2001 From: Alunara Date: Wed, 24 Sep 2025 17:13:53 +0200 Subject: [PATCH 3/6] fix: no longer return class instancs in lend-markets tanstack query --- .../components/ChartOhlcWrapper/index.tsx | 2 +- .../components/DetailInfoCrvIncentives.tsx | 2 +- .../src/lend/components/DetailInfoHealth.tsx | 2 +- .../components/MarketParameters.tsx | 2 +- .../lend/components/PageLoanCreate/Page.tsx | 6 +- .../lend/components/PageLoanManage/Page.tsx | 2 +- .../src/lend/components/PageVault/Page.tsx | 2 +- .../main/src/lend/entities/chain/chain-tvl.ts | 27 +++++---- apps/main/src/lend/entities/lend-markets.ts | 57 ++++++++++--------- apps/main/src/lend/hooks/useSupplyTotalApr.ts | 2 +- apps/main/src/lend/hooks/useVaultShares.ts | 2 +- apps/main/src/lend/store/createAppSlice.ts | 5 +- 12 files changed, 57 insertions(+), 54 deletions(-) diff --git a/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx b/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx index 9df32f089a..8a12e319db 100644 --- a/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx +++ b/apps/main/src/lend/components/ChartOhlcWrapper/index.tsx @@ -18,7 +18,7 @@ import { t } from '@ui-kit/lib/i18n' import { ChartOhlcWrapperProps, LendingMarketTokens } from './types' const ChartOhlcWrapper = ({ rChainId, userActiveKey, rOwmId, betaBackgroundColor }: ChartOhlcWrapperProps) => { - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) + 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) diff --git a/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx b/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx index 0b06c079ca..ef1d8a77a5 100644 --- a/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx +++ b/apps/main/src/lend/components/DetailInfoCrvIncentives.tsx @@ -30,7 +30,7 @@ const DetailInfoCrvIncentives = ({ lpTokenAmount: string }) => { const { tooltipValues } = useSupplyTotalApr(rChainId, rOwmId) - const gaugeAddress = useLendMarket({ chainId: rChainId, marketId: rOwmId }).data?.addresses?.gauge + const gaugeAddress = useLendMarket({ chainId: rChainId, marketId: rOwmId })?.addresses?.gauge const gaugeTotalSupply = useAbiTotalSupply(rChainId, gaugeAddress) const isGaugeAddressInvalid = gaugeAddress === zeroAddress diff --git a/apps/main/src/lend/components/DetailInfoHealth.tsx b/apps/main/src/lend/components/DetailInfoHealth.tsx index 804b0f209f..c613ee97be 100644 --- a/apps/main/src/lend/components/DetailInfoHealth.tsx +++ b/apps/main/src/lend/components/DetailInfoHealth.tsx @@ -44,7 +44,7 @@ const DetailInfoHealth = ({ loading: boolean setHealthMode: Dispatch> }) => { - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) + const market = useLendMarket({ chainId: rChainId, marketId: rOwmId }) const oraclePriceBand = useStore((state) => state.markets.pricesMapper[rChainId]?.[rOwmId]?.prices?.oraclePriceBand) const { healthFull: healthFullCurrent, diff --git a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx b/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx index 73aca56b42..9b318ef211 100644 --- a/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx +++ b/apps/main/src/lend/components/DetailsMarket/components/MarketParameters.tsx @@ -20,7 +20,7 @@ const MarketParameters = ({ rOwmId: string type: 'borrow' | 'supply' }) => { - const { data: owm } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) + 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]) diff --git a/apps/main/src/lend/components/PageLoanCreate/Page.tsx b/apps/main/src/lend/components/PageLoanCreate/Page.tsx index 7c5660fa60..a9a6463443 100644 --- a/apps/main/src/lend/components/PageLoanCreate/Page.tsx +++ b/apps/main/src/lend/components/PageLoanCreate/Page.tsx @@ -38,7 +38,7 @@ const Page = () => { const params = useParams() const { rMarket, rChainId, rFormType } = parseMarketParams(params) - const { data: market, isSuccess } = useLendMarket({ chainId: rChainId, marketId: rMarket }) + const market = useLendMarket({ chainId: rChainId, marketId: rMarket }) const { llamaApi: api = null, connectState } = useConnection() const titleMapper = useTitleMapper() const { provider, connect } = useWallet() @@ -61,11 +61,11 @@ const Page = () => { }) useEffect(() => { - if (isSuccess && !market) { + if (!market) { console.warn(`Market ${rMarket} not found. Redirecting to market list.`) push(getCollateralListPathname(params)) } - }, [isSuccess, market, params, push, rMarket]) + }, [market, params, push, rMarket]) useEffect(() => { // delay fetch rest after form details are fetched first diff --git a/apps/main/src/lend/components/PageLoanManage/Page.tsx b/apps/main/src/lend/components/PageLoanManage/Page.tsx index 0dc9dbf90a..b31223c839 100644 --- a/apps/main/src/lend/components/PageLoanManage/Page.tsx +++ b/apps/main/src/lend/components/PageLoanManage/Page.tsx @@ -41,7 +41,7 @@ const Page = () => { const { rMarket, rChainId, rFormType } = parseMarketParams(params) const { llamaApi: api = null, connectState } = useConnection() const titleMapper = useTitleMapper() - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rMarket }) + const market = useLendMarket({ chainId: rChainId, marketId: rMarket }) const rOwmId = market?.id ?? '' const userActiveKey = helpers.getUserActiveKey(api, market!) const isMdUp = useLayoutStore((state) => state.isMdUp) diff --git a/apps/main/src/lend/components/PageVault/Page.tsx b/apps/main/src/lend/components/PageVault/Page.tsx index 16a5f5a5bd..13dbf9cde4 100644 --- a/apps/main/src/lend/components/PageVault/Page.tsx +++ b/apps/main/src/lend/components/PageVault/Page.tsx @@ -42,7 +42,7 @@ const Page = () => { const { connect, provider } = useWallet() const { llamaApi: api = null, connectState } = useConnection() const titleMapper = useTitleMapper() - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rMarket }) + const market = useLendMarket({ chainId: rChainId, marketId: rMarket }) const isPageVisible = useLayoutStore((state) => state.isPageVisible) const fetchAllMarketDetails = useStore((state) => state.markets.fetchAll) diff --git a/apps/main/src/lend/entities/chain/chain-tvl.ts b/apps/main/src/lend/entities/chain/chain-tvl.ts index 34678b7d74..f177210ea1 100644 --- a/apps/main/src/lend/entities/chain/chain-tvl.ts +++ b/apps/main/src/lend/entities/chain/chain-tvl.ts @@ -1,34 +1,33 @@ 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 { useLendMarkets } from '../lend-markets' +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 => { - const { data: marketMapping = [] } = useLendMarkets({ chainId }) + const { llamaApi: api = null } = useConnection() + + const { data: marketMapping = [] } = useLendMarketMapping({ chainId }) + const markets = useMemo(() => (api ? 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( - () => marketMapping.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, @@ -36,11 +35,11 @@ export const useTvl = (chainId: ChainId | undefined): PartialQueryResult ) return { ...READY, data } }, [ - isUsdRatesError, - marketMapping, marketsCollateralMapper, marketsTotalSupplyMapper, marketsTotalDebtMapper, tokenUsdRates, + markets, + isUsdRatesError, ]) } diff --git a/apps/main/src/lend/entities/lend-markets.ts b/apps/main/src/lend/entities/lend-markets.ts index 8cca84f485..d12ca8d6ae 100644 --- a/apps/main/src/lend/entities/lend-markets.ts +++ b/apps/main/src/lend/entities/lend-markets.ts @@ -1,48 +1,53 @@ import { useMemo } from 'react' import networks from '@/lend/networks' import { ChainId } from '@/lend/types/lend.types' -import { requireLib } from '@ui-kit/features/connect-wallet' +import { fromEntries } from '@curvefi/prices-api/objects.util' +import { requireLib, useConnection } from '@ui-kit/features/connect-wallet' import { ChainParams, ChainQuery, queryFactory } from '@ui-kit/lib/model/query' import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-validation' +import type { Address } from '@ui-kit/utils' -export const { useQuery: useLendMarkets, prefetchQuery: prefetchLendMarkets } = queryFactory({ +/** + * Query 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 { useQuery: useLendMarketMapping } = queryFactory({ queryKey: ({ chainId }: ChainParams) => ['chain', { chainId }, 'lend-markets'] as const, queryFn: async ({ chainId }: ChainQuery) => { - const useAPI = chainId !== 146 // disable API for sonic const api = requireLib('llamaApi') - await api.lendMarkets.fetchMarkets(useAPI) - return api.lendMarkets - .getMarketList() - .filter((marketName) => !networks[chainId].hideMarketsInUI[marketName]) - .map((name) => api.getLendMarket(name)) + return fromEntries( + api.lendMarkets + .getMarketList() + .filter((marketName) => !networks[chainId].hideMarketsInUI[marketName]) + .map((name) => [api.getLendMarket(name).addresses.controller as Address, name]), + ) }, - staleTime: '5m', - refetchInterval: '1m', - validationSuite: llamaApiValidationSuite, + staleTime: '1h', + refetchInterval: '1h', + validationSuite: llamaApiValidationSuite({ beHydrated: true }), }) /** * 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 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 }) => { - const { data: markets, isSuccess, error } = useLendMarkets({ chainId }) + const { data: marketMapping } = useLendMarketMapping({ chainId }) + const { llamaApi: api } = useConnection() /** Create mappings from market name or controller id to market instance. */ - const mapping = useMemo( - () => - markets && - Object.fromEntries( - markets.flatMap((market) => [ - [market.name, market], - [market.addresses.controller, market], - ]), - ), - [markets], + return ( + useMemo( + () => + api?.getLendMarket(marketId) || // Try to get the market by name first, before attempting to get by mapping + (marketMapping && + marketId in marketMapping && + api?.hydrated && + api.chainId === chainId && + api.getLendMarket(marketMapping[marketId as Address])), + [api, chainId, marketId, marketMapping], + ) || undefined ) - - const market = mapping?.[marketId] - return { data: market, isSuccess, error } } diff --git a/apps/main/src/lend/hooks/useSupplyTotalApr.ts b/apps/main/src/lend/hooks/useSupplyTotalApr.ts index 5004ef425d..119ef6a452 100644 --- a/apps/main/src/lend/hooks/useSupplyTotalApr.ts +++ b/apps/main/src/lend/hooks/useSupplyTotalApr.ts @@ -8,7 +8,7 @@ import { FORMAT_OPTIONS, formatNumber } from '@ui/utils' import { useLendMarket } from '../entities/lend-markets' function useSupplyTotalApr(rChainId: ChainId, rOwmId: string) { - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) + 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 { diff --git a/apps/main/src/lend/hooks/useVaultShares.ts b/apps/main/src/lend/hooks/useVaultShares.ts index 8023e22a68..409309d528 100644 --- a/apps/main/src/lend/hooks/useVaultShares.ts +++ b/apps/main/src/lend/hooks/useVaultShares.ts @@ -12,7 +12,7 @@ function formatNumberWithPrecision(value: number, precisionDigits: number) { } function useVaultShares(rChainId: ChainId, rOwmId: string, vaultShares: string | number | undefined = '0') { - const { data: market } = useLendMarket({ chainId: rChainId, marketId: rOwmId }) + const market = useLendMarket({ chainId: rChainId, marketId: rOwmId }) const pricePerShareResp = useStore((state) => state.markets.vaultPricePerShare[rChainId]?.[rOwmId]) const { address = '', symbol = '' } = market?.borrowed_token ?? {} const { data: usdRate } = useTokenUsdRate({ chainId: rChainId, tokenAddress: address }) diff --git a/apps/main/src/lend/store/createAppSlice.ts b/apps/main/src/lend/store/createAppSlice.ts index 1cccbb934d..b65b946a2d 100644 --- a/apps/main/src/lend/store/createAppSlice.ts +++ b/apps/main/src/lend/store/createAppSlice.ts @@ -4,7 +4,6 @@ import type { GetState, SetState } from 'zustand' import type { State } from '@/lend/store/useStore' import { Api, Wallet } from '@/lend/types/lend.types' import { log } from '@ui-kit/lib/logging' -import { prefetchLendMarkets } from '../entities/lend-markets' export type DefaultStateKeys = keyof typeof DEFAULT_STATE export type SliceKey = keyof State | '' @@ -69,8 +68,8 @@ const createAppSlice = (set: SetState, get: GetState): AppSlice => state.user.resetState() } - // unfortunately, we cannot use markets from the cache as that leaves curve-lending-js in an inconsistent state - await prefetchLendMarkets({ chainId: api.chainId }) + const useAPI = api.chainId !== 146 // disable API for sonic + await api.lendMarkets.fetchMarkets(useAPI) log('Hydrating Lend - Complete') }, From 8fdc19b371fb1eb9a57e7897777006396ee8188b Mon Sep 17 00:00:00 2001 From: Alunara Date: Wed, 24 Sep 2025 18:02:47 +0200 Subject: [PATCH 4/6] fix: completely get rid of tanstack for lend-markets hooks --- .../main/src/lend/entities/chain/chain-tvl.ts | 9 ++- apps/main/src/lend/entities/lend-markets.ts | 78 +++++++++++-------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/apps/main/src/lend/entities/chain/chain-tvl.ts b/apps/main/src/lend/entities/chain/chain-tvl.ts index f177210ea1..d6497c3997 100644 --- a/apps/main/src/lend/entities/chain/chain-tvl.ts +++ b/apps/main/src/lend/entities/chain/chain-tvl.ts @@ -9,10 +9,13 @@ 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 => { - const { llamaApi: api = null } = useConnection() + const { llamaApi: api } = useConnection() - const { data: marketMapping = [] } = useLendMarketMapping({ chainId }) - const markets = useMemo(() => (api ? Object.values(marketMapping).map(api.getLendMarket) : []), [api, marketMapping]) + 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]) diff --git a/apps/main/src/lend/entities/lend-markets.ts b/apps/main/src/lend/entities/lend-markets.ts index d12ca8d6ae..4b3e20891c 100644 --- a/apps/main/src/lend/entities/lend-markets.ts +++ b/apps/main/src/lend/entities/lend-markets.ts @@ -1,31 +1,34 @@ 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 { requireLib, useConnection } from '@ui-kit/features/connect-wallet' -import { ChainParams, ChainQuery, queryFactory } from '@ui-kit/lib/model/query' -import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-validation' +import { useConnection } from '@ui-kit/features/connect-wallet' import type { Address } from '@ui-kit/utils' /** - * Query to get a mapping of lending market controller address to market name for all lending markets on a specific chain. + * 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 { useQuery: useLendMarketMapping } = queryFactory({ - queryKey: ({ chainId }: ChainParams) => ['chain', { chainId }, 'lend-markets'] as const, - queryFn: async ({ chainId }: ChainQuery) => { - const api = requireLib('llamaApi') - return fromEntries( - api.lendMarkets - .getMarketList() - .filter((marketName) => !networks[chainId].hideMarketsInUI[marketName]) - .map((name) => [api.getLendMarket(name).addresses.controller as Address, name]), - ) - }, - staleTime: '1h', - refetchInterval: '1h', - validationSuite: llamaApiValidationSuite({ beHydrated: true }), -}) +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]), + ), + // 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], + ) + + return mapping || undefined +} /** * Hook to get a specific lending market by its id or controller address. @@ -33,21 +36,28 @@ export const { useQuery: useLendMarketMapping } = queryFactory({ * @param marketId Lend market id or controller address * @returns The market instance, if found. */ -export const useLendMarket = ({ chainId, marketId }: { chainId: ChainId; marketId: string }) => { - const { data: marketMapping } = useLendMarketMapping({ chainId }) +export const useLendMarket = ({ chainId, marketId }: { chainId: ChainId; marketId: string | Address }) => { + const marketMapping = useLendMarketMapping({ chainId }) const { llamaApi: api } = useConnection() - /** Create mappings from market name or controller id to market instance. */ - return ( - useMemo( - () => - api?.getLendMarket(marketId) || // Try to get the market by name first, before attempting to get by mapping - (marketMapping && - marketId in marketMapping && - api?.hydrated && - api.chainId === chainId && - api.getLendMarket(marketMapping[marketId as Address])), - [api, chainId, marketId, marketMapping], - ) || undefined - ) + return useMemo(() => { + if (!api) return undefined + + // If markets aren't found they throw an error, but we want to return undefined instead + 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]) } From 4c718306c089045f93251c25c70018bb29321d86 Mon Sep 17 00:00:00 2001 From: Alunara Date: Wed, 24 Sep 2025 18:59:22 +0200 Subject: [PATCH 5/6] Revert "feat: add optional beHydrated prop to llamaApiValidationSuite" This reverts commit 4b2b827a593dfcc9235da70886e39699a110a0f4. --- .../borrow/queries/borrow-apy.query.ts | 2 +- .../queries/borrow-oracle-price-band.query.ts | 2 +- .../queries/borrow-oracle-price.query.ts | 2 +- .../entities/appstats-total-crvusd-supply.ts | 2 +- .../lib/model/query/curve-api-validation.ts | 23 +++++-------------- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts index 410609634d..9ccd514014 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-apy.query.ts @@ -37,6 +37,6 @@ export const { useQuery: useMarketRates } = queryFactory({ ? convertRates(await market.stats.rates()) : convertRates({ borrowApr: (await market.stats.parameters()).rate }) }, - validationSuite: llamaApiValidationSuite(), + validationSuite: llamaApiValidationSuite, dependencies: (params) => [maxBorrowReceiveKey(params)], }) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts index b7ff40f93d..fed9837102 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price-band.query.ts @@ -7,5 +7,5 @@ export const { useQuery: useBorrowOraclePriceBand } = queryFactory({ queryKey: ({ chainId, poolId }: PoolParams) => [...rootKeys.pool({ chainId, poolId }), 'oraclePriceBand'] as const, queryFn: ({ poolId }: PoolQuery): Promise => getLlamaMarket(poolId).oraclePriceBand(), - validationSuite: llamaApiValidationSuite(), + validationSuite: llamaApiValidationSuite, }) diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts index 0816df3589..dc38e406d0 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-oracle-price.query.ts @@ -7,5 +7,5 @@ export const { useQuery: useBorrowOraclePrice } = queryFactory({ queryKey: ({ chainId, poolId }: PoolParams) => [...rootKeys.pool({ chainId, poolId }), 'oraclePrice'] as const, queryFn: ({ poolId }: PoolQuery): Promise => getLlamaMarket(poolId).oraclePrice(), - validationSuite: llamaApiValidationSuite(), + validationSuite: llamaApiValidationSuite, }) diff --git a/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts b/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts index dc603d361a..6cbee3460a 100644 --- a/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts +++ b/apps/main/src/loan/entities/appstats-total-crvusd-supply.ts @@ -8,5 +8,5 @@ import { llamaApiValidationSuite } from '@ui-kit/lib/model/query/curve-api-valid export const { useQuery: useAppStatsTotalCrvusdSupply } = queryFactory({ queryKey: (params: ChainParams) => ['appStatsTotalCrvusdSupply', { chainId: params.chainId }] as const, queryFn: ({ chainId }: ChainQuery) => networks[chainId].api.helpers.getTotalSupply(requireLib('llamaApi')), - validationSuite: llamaApiValidationSuite(), + validationSuite: llamaApiValidationSuite, }) diff --git a/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts b/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts index a1b235f082..28e45a1dcf 100644 --- a/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts +++ b/packages/curve-ui-kit/src/lib/model/query/curve-api-validation.ts @@ -20,29 +20,18 @@ export const curveApiValidationSuite = createValidationSuite( }, ) -type ApiValidationParams = { - /** The API should be fully hydrated. */ - beHydrated?: boolean -} - export const llamaApiValidationGroup = ({ chainId, - beHydrated, -}: ChainParams & ApiValidationParams) => +}: ChainParams) => group('apiValidation', () => { test('api', 'API chain ID mismatch', () => { enforce(getLib('llamaApi')?.chainId).message('Chain ID should be loaded').equals(chainId) }) - - if (beHydrated) { - test('apiHydrated', 'API should be hydrated', () => { - enforce(getLib('llamaApi')?.hydrated).message('API should be hydrated').isTrue() - }) - } }) -export const llamaApiValidationSuite = (apiValidationParams: ApiValidationParams = { beHydrated: false }) => - createValidationSuite((params: ChainParams) => { +export const llamaApiValidationSuite = createValidationSuite( + (params: ChainParams) => { chainValidationGroup(params) - llamaApiValidationGroup({ ...params, ...apiValidationParams }) - }) + llamaApiValidationGroup(params) + }, +) From c97a829f357d82906e429f51b1fb7e0642d70404 Mon Sep 17 00:00:00 2001 From: Alunara Date: Wed, 24 Sep 2025 19:48:48 +0200 Subject: [PATCH 6/6] fix: instant redirect back when not hydrated --- apps/main/src/lend/components/PageLoanCreate/Page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/main/src/lend/components/PageLoanCreate/Page.tsx b/apps/main/src/lend/components/PageLoanCreate/Page.tsx index a9a6463443..fbb5525ebb 100644 --- a/apps/main/src/lend/components/PageLoanCreate/Page.tsx +++ b/apps/main/src/lend/components/PageLoanCreate/Page.tsx @@ -61,11 +61,11 @@ const Page = () => { }) useEffect(() => { - if (!market) { + if (api?.hydrated && !market) { console.warn(`Market ${rMarket} not found. Redirecting to market list.`) push(getCollateralListPathname(params)) } - }, [market, params, push, rMarket]) + }, [api?.hydrated, market, params, push, rMarket]) useEffect(() => { // delay fetch rest after form details are fetched first