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
73 changes: 37 additions & 36 deletions apps/main/src/loan/components/PageLoanCreate/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ChartOhlcWrapper from '@/loan/components/ChartOhlcWrapper'
import { MarketInformationComp } from '@/loan/components/MarketInformationComp'
import LoanCreate from '@/loan/components/PageLoanCreate/index'
import { hasLeverage } from '@/loan/components/PageLoanCreate/utils'
import { useMintMarket } from '@/loan/entities/mint-markets'
import { useMarketDetails } from '@/loan/hooks/useMarketDetails'
import useStore from '@/loan/store/useStore'
import { type CollateralUrlParams, type LlamaApi, Llamma } from '@/loan/types/loan.types'
Expand Down Expand Up @@ -48,12 +49,12 @@ const Page = () => {
const { address } = useAccount()
const [loaded, setLoaded] = useState(false)

const collateralDatasMapper = useStore((state) => state.collaterals.collateralDatasMapper[rChainId])
const market = useMintMarket({ chainId: rChainId, marketId: rCollateralId })
const marketId = market?.id ?? ''
const pageLoaded = !isLoading(connectState)
const { llamma, llamma: { id: llammaId = '' } = {}, displayName } = collateralDatasMapper?.[rCollateralId] ?? {}

const formValues = useStore((state) => state.loanCreate.formValues)
const { data: loanExists } = useLoanExists({ chainId: rChainId, marketId: llammaId, userAddress: address })
const { data: loanExists } = useLoanExists({ chainId: rChainId, marketId, userAddress: address })
const isMdUp = useLayoutStore((state) => state.isMdUp)
const isPageVisible = useLayoutStore((state) => state.isPageVisible)
const fetchLoanDetails = useStore((state) => state.loans.fetchLoanDetails)
Expand All @@ -65,10 +66,10 @@ const Page = () => {

const maxSlippage = useUserProfileStore((state) => state.maxSlippage.crypto)

const isReady = !!collateralDatasMapper
const isReady = !!market
const isLeverage = rFormType === 'leverage'

const marketDetails = useMarketDetails({ chainId: rChainId, llamma, llammaId })
const marketDetails = useMarketDetails({ chainId: rChainId, llamma: market, llammaId: marketId })

const fetchInitial = useCallback(
(curve: LlamaApi, isLeverage: boolean, llamma: Llamma) => {
Expand All @@ -95,48 +96,45 @@ const Page = () => {

useEffect(() => {
if (pageLoaded && curve?.hydrated) {
if (llamma) {
resetUserDetailsState(llamma)
fetchInitial(curve, isLeverage, llamma)
void fetchLoanDetails(curve, llamma)
if (market) {
resetUserDetailsState(market)
fetchInitial(curve, isLeverage, market)
void fetchLoanDetails(curve, market)
setLoaded(true)
} else if (collateralDatasMapper) {
console.warn(
`Collateral ${rCollateralId} not found for chain ${rChainId}. Redirecting to market list.`,
collateralDatasMapper,
)
} else {
console.warn(`Collateral ${rCollateralId} not found for chain ${rChainId}. Redirecting to market list.`)
push(getCollateralListPathname(params))
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageLoaded && curve?.hydrated && llamma])
}, [pageLoaded && curve?.hydrated && market])

// redirect if loan exists
useEffect(() => {
if (!loaded && llamma && loanExists) {
push(getLoanManagePathname(params, llamma.id, 'loan'))
if (!loaded && market && loanExists) {
push(getLoanManagePathname(params, market.id, 'loan'))
}
}, [llamma, loaded, loanExists, params, push])
}, [loaded, loanExists, market, params, push])

// redirect if form is leverage but no leverage option
useEffect(() => {
if (llamma && rFormType === 'leverage' && !hasLeverage(llamma)) {
push(getLoanCreatePathname(params, llamma.id))
if (market && rFormType === 'leverage' && !hasLeverage(market)) {
push(getLoanCreatePathname(params, market.id))
}
}, [loaded, rFormType, llamma, push, params])
}, [loaded, rFormType, market, push, params])

// max slippage updated
useEffect(() => {
if (loaded && !!curve) {
void setFormValues(curve, isLeverage, llamma, formValues, maxSlippage)
if (loaded && !!curve && market) {
void setFormValues(curve, isLeverage, market, formValues, maxSlippage)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [maxSlippage])

usePageVisibleInterval(
() => {
if (isPageVisible && curve && llamma) {
void fetchLoanDetails(curve, llamma)
if (isPageVisible && curve && market) {
void fetchLoanDetails(curve, market)
}
},
REFRESH_INTERVAL['1m'],
Expand All @@ -151,7 +149,8 @@ const Page = () => {

const TitleComp = () => (
<AppPageFormTitleWrapper>
<Title>{displayName || getTokenName(llamma).collateral}</Title>
{/** TODO: generalize or re-use existing market counting technique, see `createCountMarket` in llama-markets.ts */}
<Title>{market?.id === 'sfrxeth2' ? 'sfrxETH v2' : getTokenName(market).collateral}</Title>
</AppPageFormTitleWrapper>
)

Expand All @@ -172,7 +171,7 @@ const Page = () => {
</ExpandButton>
</Box>
<PriceAndTradesExpandedWrapper variant="secondary">
<ChartOhlcWrapper rChainId={rChainId} llamma={llamma} llammaId={llammaId} />
<ChartOhlcWrapper rChainId={rChainId} llamma={market ?? null} llammaId={marketId} />
</PriceAndTradesExpandedWrapper>
</PriceAndTradesExpandedContainer>
)}
Expand All @@ -184,8 +183,8 @@ const Page = () => {
isReady={isReady}
isLeverage={isLeverage}
loanExists={loanExists}
llamma={llamma}
llammaId={llammaId}
llamma={market ?? null}
llammaId={marketId}
params={params}
rChainId={rChainId}
rCollateralId={rCollateralId}
Expand All @@ -202,13 +201,15 @@ const Page = () => {
)}
<Stack>
<MarketDetails {...marketDetails} />
<MarketInformationComp
llamma={llamma}
llammaId={llammaId}
chainId={rChainId}
chartExpanded={chartExpanded}
page="create"
/>
{market && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this added? It results in the page jumping during load

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For some reason the old code type asserted that llamma couldn't be undefined. But I'm not sure if that assertion is valid. How can it be asserted to not be null when stuff is being loaded? Either the assertion is somehow true and this won't jump, or stuff was being shown prematurely in the old verison. I'd rather keep this check to make sure there's no errors. If people never complained about the page jumping it's probably a safe assumption?

Copy link
Collaborator

Choose a reason for hiding this comment

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

People dont complain because it works, it just looks unprofessional

<MarketInformationComp
llamma={market}
llammaId={marketId}
chainId={rChainId}
chartExpanded={chartExpanded}
page="create"
/>
)}
</Stack>
</Stack>
</DetailPageStack>
Expand Down
59 changes: 31 additions & 28 deletions apps/main/src/loan/components/PageLoanManage/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MarketInformationComp } from '@/loan/components/MarketInformationComp'
import LoanMange from '@/loan/components/PageLoanManage/index'
import type { FormType } from '@/loan/components/PageLoanManage/types'
import { hasDeleverage } from '@/loan/components/PageLoanManage/utils'
import { useMintMarket } from '@/loan/entities/mint-markets'
import { useLoanPositionDetails } from '@/loan/hooks/useLoanPositionDetails'
import { useMarketDetails } from '@/loan/hooks/useMarketDetails'
import useTitleMapper from '@/loan/hooks/useTitleMapper'
Expand Down Expand Up @@ -47,12 +48,12 @@ const Page = () => {
const rChainId = useChainId(params)
const { address } = useAccount()

const { llamma } = useStore((state) => state.collaterals.collateralDatasMapper[rChainId]?.[rCollateralId]) ?? {}
const llammaId = llamma?.id || ''
const market = useMintMarket({ chainId: rChainId, marketId: rCollateralId })
const marketId = market?.id ?? ''

const isMdUp = useLayoutStore((state) => state.isMdUp)
const isPageVisible = useLayoutStore((state) => state.isPageVisible)
const { data: loanExists } = useLoanExists({ chainId: rChainId, marketId: llammaId, userAddress: address })
const { data: loanExists } = useLoanExists({ chainId: rChainId, marketId, userAddress: address })
const fetchLoanDetails = useStore((state) => state.loans.fetchLoanDetails)
const fetchUserLoanDetails = useStore((state) => state.loans.fetchUserLoanDetails)
const resetUserDetailsState = useStore((state) => state.loans.resetUserDetailsState)
Expand All @@ -62,28 +63,28 @@ const Page = () => {
const [loaded, setLoaded] = useState(false)

const isValidRouterParams = !!rChainId && !!rCollateralId && !!rFormType
const isReady = !!curve?.signerAddress && !!llamma
const isReady = !!curve?.signerAddress && !!market

const marketDetails = useMarketDetails({ chainId: rChainId, llamma, llammaId })
const marketDetails = useMarketDetails({ chainId: rChainId, llamma: market, llammaId: marketId })
const positionDetails = useLoanPositionDetails({
chainId: rChainId,
llamma,
llammaId,
llamma: market,
llammaId: marketId,
})

useEffect(() => {
if (curve?.hydrated && pageLoaded) {
if (rChainId && rCollateralId && rFormType && curve.signerAddress && llamma) {
if (rChainId && rCollateralId && rFormType && curve.signerAddress && market) {
void (async () => {
const fetchedLoanDetails = await fetchLoanDetails(curve, llamma)
const fetchedLoanDetails = await fetchLoanDetails(curve, market)
if (!fetchedLoanDetails.loanExists) {
resetUserDetailsState(llamma)
resetUserDetailsState(market)
push(getLoanCreatePathname(params, rCollateralId))
}
setLoaded(true)
})()
} else {
const args = { rChainId, rCollateralId, rFormType, signer: curve.signerAddress, llamma }
const args = { rChainId, rCollateralId, rFormType, signer: curve.signerAddress, market }
console.warn(`Cannot find market ${rCollateralId}, redirecting to list`, args)
push(getCollateralListPathname(params))
}
Expand All @@ -93,17 +94,17 @@ const Page = () => {

// redirect if form is deleverage but no deleverage option
useEffect(() => {
if (llamma && rFormType === 'deleverage' && !hasDeleverage(llamma)) {
push(getLoanCreatePathname(params, llamma.id))
if (market && rFormType === 'deleverage' && !hasDeleverage(market)) {
push(getLoanCreatePathname(params, market.id))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loaded, rFormType, llamma])
}, [loaded, rFormType, market])

usePageVisibleInterval(
() => {
if (isPageVisible && curve && !!curve.signerAddress && llamma && loanExists) {
void fetchLoanDetails(curve, llamma)
void fetchUserLoanDetails(curve, llamma)
if (isPageVisible && curve && !!curve.signerAddress && market && loanExists) {
void fetchLoanDetails(curve, market)
void fetchUserLoanDetails(curve, market)
}
},
REFRESH_INTERVAL['1m'],
Expand All @@ -118,9 +119,9 @@ const Page = () => {

const formProps = {
curve,
isReady: !!curve?.signerAddress && !!llamma,
llamma,
llammaId,
isReady: !!curve?.signerAddress && !!market,
llamma: market ?? null,
llammaId: marketId,
rChainId,
}

Expand All @@ -140,7 +141,7 @@ const Page = () => {
</ExpandButton>
</Box>
<PriceAndTradesExpandedWrapper variant="secondary">
<ChartOhlcWrapper rChainId={rChainId} llamma={llamma} llammaId={llammaId} />
<ChartOhlcWrapper rChainId={rChainId} llamma={market ?? null} llammaId={marketId} />
</PriceAndTradesExpandedWrapper>
</PriceAndTradesExpandedContainer>
)}
Expand Down Expand Up @@ -169,13 +170,15 @@ const Page = () => {
)}
<Stack>
<MarketDetails {...marketDetails} />
<MarketInformationComp
llamma={llamma}
llammaId={llammaId}
chainId={rChainId}
chartExpanded={chartExpanded}
page="manage"
/>
{market && (
<MarketInformationComp
llamma={market}
llammaId={marketId}
chainId={rChainId}
chartExpanded={chartExpanded}
page="manage"
/>
)}
</Stack>
</Stack>
</DetailPageStack>
Expand Down
57 changes: 57 additions & 0 deletions apps/main/src/loan/entities/mint-markets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useMemo } from 'react'
import { isAddress } from 'viem'
import { ChainId } from '@/loan/types/loan.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 mint market controller address to market name for all mint markets on a specific chain.
* Primarily useful fetching mint markets via URL.
*/
export const useMintMarketMapping = ({ chainId }: { chainId: ChainId | undefined }) => {
const { llamaApi: api } = useConnection()
const mapping = useMemo(
() =>
api?.hydrated &&
chainId &&
fromEntries(api.mintMarkets.getMarketList().map((name) => [api.getMintMarket(name).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 mint market by its id or controller address.
* @param chainId The chain for which to get the mint market of
* @param marketId Mint market id or controller address
* @returns The market instance, if found.
*/
export const useMintMarket = ({ chainId, marketId }: { chainId: ChainId; marketId: string | Address }) => {
const marketMapping = useMintMarketMapping({ 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.

no please, ignoring errors is asking for tricky bugs.
If you want to catch invalid market IDs please check the exact exception and return a proper error state, not undefined

const safeGetMarket = (id: string) => {
try {
return api.getMintMarket(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])
}
Loading
Loading