diff --git a/apps/main/src/llamalend/features/borrow/components/CreateLoanForm.tsx b/apps/main/src/llamalend/features/borrow/components/CreateLoanForm.tsx index c5719ecfbb..789ff6409c 100644 --- a/apps/main/src/llamalend/features/borrow/components/CreateLoanForm.tsx +++ b/apps/main/src/llamalend/features/borrow/components/CreateLoanForm.tsx @@ -56,19 +56,19 @@ export const CreateLoanForm = ({ const network = networks[chainId] const [preset, setPreset] = useBorrowPreset(BorrowPreset.Safe) const { + form, values, - onSubmit, + params, isPending, + onSubmit, maxTokenValues, - params, - form, - collateralToken, borrowToken, + collateralToken, isCreated, creationError, txHash, - formErrors, isApproved, + formErrors, } = useCreateLoanForm({ market, network, preset, onCreated }) const setRange = useCallback( (range: number) => { diff --git a/apps/main/src/llamalend/features/borrow/components/RepayLoanInfoAccordion.tsx b/apps/main/src/llamalend/features/borrow/components/RepayLoanInfoAccordion.tsx new file mode 100644 index 0000000000..3e3e876186 --- /dev/null +++ b/apps/main/src/llamalend/features/borrow/components/RepayLoanInfoAccordion.tsx @@ -0,0 +1,81 @@ +import type { Token } from '@/llamalend/features/borrow/types' +import { useLoanToValueFromUserState } from '@/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState' +import { useHealthQueries } from '@/llamalend/hooks/useHealthQueries' +import type { NetworkDict } from '@/llamalend/llamalend.types' +import { useMarketFutureRates } from '@/llamalend/queries/market-future-rates.query' +import { useMarketRates } from '@/llamalend/queries/market-rates' +import { useRepayBands } from '@/llamalend/queries/repay/repay-bands.query' +import { useRepayExpectedBorrowed } from '@/llamalend/queries/repay/repay-expected-borrowed.query' +import { useRepayEstimateGas } from '@/llamalend/queries/repay/repay-gas-estimate.query' +import { useRepayHealth } from '@/llamalend/queries/repay/repay-health.query' +import { useRepayPriceImpact } from '@/llamalend/queries/repay/repay-price-impact.query' +import { useRepayPrices } from '@/llamalend/queries/repay/repay-prices.query' +import { getUserHealthOptions } from '@/llamalend/queries/user-health.query' +import { useUserState } from '@/llamalend/queries/user-state.query' +import type { RepayParams } from '@/llamalend/queries/validation/manage-loan.types' +import type { RepayForm } from '@/llamalend/queries/validation/manage-loan.validation' +import { LoanInfoAccordion } from '@/llamalend/widgets/manage-loan/LoanInfoAccordion' +import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { useSwitch } from '@ui-kit/hooks/useSwitch' +import { q } from '@ui-kit/types/util' +import { decimal, Decimal } from '@ui-kit/utils' + +export function RepayLoanInfoAccordion({ + params, + values: { slippage, userCollateral, userBorrowed }, + collateralToken, + borrowToken, + networks, + onSlippageChange, + hasLeverage, +}: { + params: RepayParams + values: RepayForm + collateralToken: Token | undefined + borrowToken: Token | undefined + networks: NetworkDict + onSlippageChange: (newSlippage: Decimal) => void + hasLeverage: boolean | undefined +}) { + const [isOpen, , , toggle] = useSwitch(false) + const userState = q(useUserState(params, isOpen)) + return ( + getUserHealthOptions({ ...params, isFull }))} + prevRates={q(useMarketRates(params, isOpen))} + rates={q(useMarketFutureRates(params, isOpen))} + debt={{ + ...userState, + data: userState?.data?.debt && decimal(+userState.data.debt - +(userBorrowed ?? 0)), + tokenSymbol: borrowToken?.symbol, + }} + prevDebt={{ ...userState, data: userState?.data?.debt }} + prices={q(useRepayPrices(params, isOpen))} + // routeImage={q(useRepayRouteImage(params, isOpen))} + loanToValue={useLoanToValueFromUserState( + { + chainId: params.chainId, + marketId: params.marketId, + userAddress: params.userAddress, + collateralToken, + borrowToken, + collateralDelta: userCollateral && `${-+userCollateral}`, + }, + isOpen, + )} + leverage={{ + enabled: !!hasLeverage, + expectedBorrowed: useRepayExpectedBorrowed(params, isOpen), + priceImpact: useRepayPriceImpact(params, isOpen), + slippage, + onSlippageChange, + collateralSymbol: collateralToken?.symbol, + }} + /> + ) +} diff --git a/apps/main/src/llamalend/features/manage-loan/components/AddCollateralForm.tsx b/apps/main/src/llamalend/features/manage-loan/components/AddCollateralForm.tsx index 2b7996d65b..46c4d4eb3d 100644 --- a/apps/main/src/llamalend/features/manage-loan/components/AddCollateralForm.tsx +++ b/apps/main/src/llamalend/features/manage-loan/components/AddCollateralForm.tsx @@ -68,15 +68,17 @@ export const AddCollateralForm = ({ bands={bands} prices={prices} rates={marketRates} - loanToValue={useLoanToValueFromUserState({ - chainId: params.chainId!, - marketId: params.marketId, - userAddress: params.userAddress, - collateralToken, - borrowToken, - enabled: isOpen, - collateralDelta: values.userCollateral, - })} + loanToValue={useLoanToValueFromUserState( + { + chainId: params.chainId, + marketId: params.marketId, + userAddress: params.userAddress, + collateralToken, + borrowToken, + collateralDelta: values.userCollateral, + }, + isOpen, + )} gas={gas} /> } diff --git a/apps/main/src/llamalend/features/manage-loan/components/RemoveCollateralForm.tsx b/apps/main/src/llamalend/features/manage-loan/components/RemoveCollateralForm.tsx index 9041ad73ab..0937226458 100644 --- a/apps/main/src/llamalend/features/manage-loan/components/RemoveCollateralForm.tsx +++ b/apps/main/src/llamalend/features/manage-loan/components/RemoveCollateralForm.tsx @@ -70,18 +70,20 @@ export const RemoveCollateralForm = ({ bands={bands} prices={prices} rates={marketRates} - loanToValue={useLoanToValueFromUserState({ - chainId: params.chainId!, - marketId: params.marketId, - userAddress: params.userAddress, - collateralToken, - borrowToken, - enabled: isOpen, - collateralDelta: - values.userCollateral != null - ? (`-${values.userCollateral}` as unknown as import('@ui-kit/utils').Decimal) - : undefined, - })} + loanToValue={useLoanToValueFromUserState( + { + chainId: params.chainId, + marketId: params.marketId, + userAddress: params.userAddress, + collateralToken, + borrowToken, + collateralDelta: + values.userCollateral != null + ? (`-${values.userCollateral}` as unknown as import('@ui-kit/utils').Decimal) + : undefined, + }, + isOpen, + )} gas={gas} /> } diff --git a/apps/main/src/llamalend/features/manage-loan/components/RepayForm.tsx b/apps/main/src/llamalend/features/manage-loan/components/RepayForm.tsx index 2a3eead762..f937aeb1df 100644 --- a/apps/main/src/llamalend/features/manage-loan/components/RepayForm.tsx +++ b/apps/main/src/llamalend/features/manage-loan/components/RepayForm.tsx @@ -1,15 +1,17 @@ -import { useLoanToValueFromUserState } from '@/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState' +import { RepayLoanInfoAccordion } from '@/llamalend/features/borrow/components/RepayLoanInfoAccordion' +import { setValueOptions } from '@/llamalend/features/borrow/react-form.utils' +import { hasDeleverage, hasLeverage } from '@/llamalend/llama.utils' import type { LlamaMarketTemplate, NetworkDict } from '@/llamalend/llamalend.types' import type { RepayOptions } from '@/llamalend/mutations/repay.mutation' -import { useMarketRates } from '@/llamalend/queries/market-rates' import { LoanFormAlerts } from '@/llamalend/widgets/manage-loan/LoanFormAlerts' import { LoanFormTokenInput } from '@/llamalend/widgets/manage-loan/LoanFormTokenInput' import { LoanFormWrapper } from '@/llamalend/widgets/manage-loan/LoanFormWrapper' -import { LoanInfoAccordion } from '@/llamalend/widgets/manage-loan/LoanInfoAccordion' import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' +import { notFalsy } from '@curvefi/prices-api/objects.util' import Button from '@mui/material/Button' +import Checkbox from '@mui/material/Checkbox' +import FormControlLabel from '@mui/material/FormControlLabel' import Stack from '@mui/material/Stack' -import { useSwitch } from '@ui-kit/hooks/useSwitch' import { t } from '@ui-kit/lib/i18n' import { InputDivider } from '../../../widgets/InputDivider' import { useRepayForm } from '../hooks/useRepayForm' @@ -22,7 +24,7 @@ export const RepayForm = ({ onRepaid, fromCollateral, fromWallet, - fromBorrowed, + fromBorrowed: showUserBorrowed, }: { market: LlamaMarketTemplate | undefined networks: NetworkDict @@ -34,28 +36,21 @@ export const RepayForm = ({ fromBorrowed?: boolean }) => { const network = networks[chainId] - const [isOpen, , , toggle] = useSwitch(false) - const { form, + values, + params, isPending, onSubmit, - action, - bands, - health, - prices, - gas, isDisabled, - isFull, - formErrors, - collateralToken, borrowToken, - params, - values, + collateralToken, + isRepaid, + repayError, txHash, - expectedBorrowed, - routeImage, - priceImpact, + isApproved, + formErrors, + isFull, } = useRepayForm({ market, network, @@ -63,36 +58,27 @@ export const RepayForm = ({ enabled, onRepaid, }) - - const marketRates = useMarketRates(params, isOpen) - + const { withdrawEnabled: withdrawEnabled } = values + const showStateCollateral = market && hasLeverage(market) && fromCollateral + const showUserCollateral = market && (hasLeverage(market) || hasDeleverage(market)) && fromWallet return ( - form.setValue('slippage', value, setValueOptions)} + hasLeverage={market && hasLeverage(market)} /> } > - }> - {fromCollateral && ( + : undefined}> + {showStateCollateral && ( ({ network={network} /> )} - {fromWallet && ( + {showUserCollateral && ( ({ network={network} /> )} - {fromBorrowed && ( + {showUserBorrowed && ( ({ )} + form.setValue('withdrawEnabled', e.target.checked, setValueOptions)} + /> + } + label={t`Repay & Withdraw`} + /> diff --git a/apps/main/src/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState.ts b/apps/main/src/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState.ts index 7a76f607dd..9b715e9e22 100644 --- a/apps/main/src/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState.ts +++ b/apps/main/src/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState.ts @@ -7,12 +7,11 @@ import type { Decimal } from '@ui-kit/utils' import type { Token } from '../../borrow/types' type Params = { - chainId: ChainId + chainId: ChainId | null | undefined marketId: string | null | undefined userAddress: Address | null | undefined collateralToken: Token | undefined borrowToken: Token | undefined - enabled: boolean /** * Net change applied to on-chain collateral (positive = adding, negative = removing). * TODO: use expectedCollateral from llamalend-js, currently being implemented by @0xPearce @@ -29,16 +28,10 @@ type Params = { * It uses the generic userState query so it can be reused across * add-collateral, remove-collateral and repay flows. */ -export const useLoanToValueFromUserState = ({ - chainId, - marketId, - userAddress, - collateralToken, - borrowToken, - enabled, - collateralDelta, - expectedBorrowed, -}: Params) => { +export const useLoanToValueFromUserState = ( + { chainId, marketId, userAddress, collateralToken, borrowToken, collateralDelta, expectedBorrowed }: Params, + enabled: boolean, +) => { const { data: userState, isLoading: isUserLoading, diff --git a/apps/main/src/llamalend/features/manage-loan/hooks/useRepayForm.ts b/apps/main/src/llamalend/features/manage-loan/hooks/useRepayForm.ts index b4b39daf6b..ad1c91ceb8 100644 --- a/apps/main/src/llamalend/features/manage-loan/hooks/useRepayForm.ts +++ b/apps/main/src/llamalend/features/manage-loan/hooks/useRepayForm.ts @@ -2,26 +2,20 @@ import { useEffect, useMemo } from 'react' import type { UseFormReturn } from 'react-hook-form' import { useForm } from 'react-hook-form' import { useConnection } from 'wagmi' -import { useHealthQueries } from '@/llamalend/hooks/useHealthQueries' -import { getTokens } from '@/llamalend/llama.utils' +import { getTokens, hasLeverage } from '@/llamalend/llama.utils' import type { LlamaMarketTemplate, NetworkDict } from '@/llamalend/llamalend.types' import { type RepayOptions, useRepayMutation } from '@/llamalend/mutations/repay.mutation' -import { useRepayBands } from '@/llamalend/queries/repay/repay-bands.query' -import { useRepayExpectedBorrowed } from '@/llamalend/queries/repay/repay-expected-borrowed.query' -import { useRepayEstimateGas } from '@/llamalend/queries/repay/repay-gas-estimate.query' -import { getRepayHealthOptions } from '@/llamalend/queries/repay/repay-health.query' +import { useBorrowCreateLoanIsApproved } from '@/llamalend/queries/create-loan/borrow-create-loan-approved.query' import { useRepayIsAvailable } from '@/llamalend/queries/repay/repay-is-available.query' import { useRepayIsFull } from '@/llamalend/queries/repay/repay-is-full.query' -import { useRepayPriceImpact } from '@/llamalend/queries/repay/repay-price-impact.query' -import { useRepayPrices } from '@/llamalend/queries/repay/repay-prices.query' -import { useRepayRouteImage } from '@/llamalend/queries/repay/repay-route-image.query' -import type { RepayFromCollateralIsFullParams } from '@/llamalend/queries/validation/manage-loan.types' +import type { RepayIsFullParams } from '@/llamalend/queries/validation/manage-loan.types' import { type RepayForm, repayFormValidationSuite } from '@/llamalend/queries/validation/manage-loan.validation' import type { IChainId as LlamaChainId, INetworkName as LlamaNetworkId } from '@curvefi/llamalend-api/lib/interfaces' import { vestResolver } from '@hookform/resolvers/vest' import { useDebouncedValue } from '@ui-kit/hooks/useDebounce' import { formDefaultOptions } from '@ui-kit/lib/model' -import { useFormErrors } from '../../borrow/react-form.utils' +import { SLIPPAGE_PRESETS } from '@ui-kit/widgets/SlippageSettings/slippage.utils' +import { setValueOptions, useFormErrors } from '../../borrow/react-form.utils' const useCallbackAfterFormUpdate = (form: UseFormReturn, callback: () => void) => useEffect(() => form.subscribe({ formState: { values: true }, callback }), [form, callback]) @@ -37,7 +31,7 @@ export const useRepayForm = enabled?: boolean - onRepaid?: RepayOptions['onRepaid'] + onRepaid?: NonNullable }) => { const { address: userAddress } = useConnection() const { chainId } = network @@ -54,7 +48,10 @@ export const useRepayForm = => ({ - chainId, - marketId, - userAddress, - stateCollateral: values.stateCollateral, - userCollateral: values.userCollateral, - userBorrowed: values.userBorrowed, - isFull: values.isFull, - }), - [ - chainId, - marketId, - userAddress, - values.stateCollateral, - values.userCollateral, - values.userBorrowed, - values.isFull, - ], + (): RepayIsFullParams => ({ chainId, marketId, userAddress, ...values }), + [chainId, marketId, userAddress, values], ), ) - const { onSubmit, ...action } = useRepayMutation({ network, marketId, onRepaid, onReset: form.reset, userAddress }) + const { + onSubmit, + isPending: isRepaying, + isSuccess: isRepaid, + error: repayError, + data, + reset: resetRepay, + } = useRepayMutation({ + network, + marketId, + onRepaid, + onReset: form.reset, + userAddress, + }) - useCallbackAfterFormUpdate(form, action.reset) + useCallbackAfterFormUpdate(form, resetRepay) // reset mutation state on form change - const bands = useRepayBands(params, enabled) - const expectedBorrowed = useRepayExpectedBorrowed(params, enabled) - const health = useHealthQueries((isFull) => getRepayHealthOptions({ ...params, isFull }, enabled)) const isAvailable = useRepayIsAvailable(params, enabled) const isFull = useRepayIsFull(params, enabled) - const priceImpact = useRepayPriceImpact(params, enabled) - const prices = useRepayPrices(params, enabled) - const routeImage = useRepayRouteImage(params, enabled) - const gas = useRepayEstimateGas(networks, params, enabled) const formErrors = useFormErrors(form.formState) - useEffect(() => form.setValue('isFull', isFull.data, { shouldValidate: true }), [form, isFull.data]) + useEffect(() => form.setValue('isFull', isFull.data, setValueOptions), [form, isFull.data]) + + // todo: remove from form, move this to queries directly as they depend on market only + useEffect(() => market && form.setValue('leverageEnabled', hasLeverage(market), setValueOptions), [market, form]) return { form, values, params, - isPending: form.formState.isSubmitting || action.isPending, + isPending: form.formState.isSubmitting || isRepaying, onSubmit: form.handleSubmit(onSubmit), - action, - bands, - expectedBorrowed, - health, isDisabled: !isAvailable.data || formErrors.length > 0, - isFull, - priceImpact, - prices, - routeImage, - gas, - txHash: action.data?.hash, - collateralToken, borrowToken, - formErrors, + collateralToken, + isRepaid, + repayError, + txHash: data?.hash, + isApproved: useBorrowCreateLoanIsApproved(params), + formErrors: useFormErrors(form.formState), + isFull, } } diff --git a/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useActionInfos.ts b/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useActionInfos.ts index da21a506ae..3e725c6d5c 100644 --- a/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useActionInfos.ts +++ b/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useActionInfos.ts @@ -12,25 +12,11 @@ import { useLoanInfo } from './useLoanInfo' */ export function useActionInfos(params: MarketParams): ActionInfosProps { const { address: userAddress } = useConnection() - const { data: userHealth } = useHealthQueries((isFull) => - getUserHealthOptions( - { - ...{ ...params, userAddress }, - isFull, - }, - undefined, - ), - ) - - const health = { current: Number(userHealth ?? 0) } - - const loanInfo = useLoanInfo(params) - const collateral = useCollateralInfo(params) - + const { data: userHealth } = useHealthQueries((isFull) => getUserHealthOptions({ ...params, userAddress, isFull })) return { - health, - loanInfo, - collateral, + health: { current: Number(userHealth ?? 0) }, + loanInfo: useLoanInfo(params), + collateral: useCollateralInfo(params), transaction: { estimatedTxCost: { eth: 0.0024, gwei: 0.72, dollars: 0.48 }, }, diff --git a/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useImproveHealthTab.ts b/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useImproveHealthTab.ts index 449a42daa6..4842018cc1 100644 --- a/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useImproveHealthTab.ts +++ b/apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useImproveHealthTab.ts @@ -21,6 +21,7 @@ export function useImproveHealthTab(params: MarketParams): ImproveHealthProps { userCollateral: '0' as Decimal, userBorrowed: debt, isFull: false, // todo: implement full repays + leverageEnabled: false, // todo: implement leverage }) }, [mutate], diff --git a/apps/main/src/llamalend/llama.utils.ts b/apps/main/src/llamalend/llama.utils.ts index f1a98c8d3a..3f109b77e7 100644 --- a/apps/main/src/llamalend/llama.utils.ts +++ b/apps/main/src/llamalend/llama.utils.ts @@ -24,9 +24,19 @@ export const getLlamaMarket = (id: string, lib = requireLib('llamaApi')): LlamaM * - Mint Market and either its `leverageZap` is not the zero address or its `leverageV2` property has leverage */ export const hasLeverage = (market: LlamaMarketTemplate) => - market instanceof LendMarketTemplate - ? market.leverage.hasLeverage() - : market.leverageZap !== zeroAddress || market.leverageV2.hasLeverage() + hasV1Leverage(market) || (market instanceof MintMarketTemplate && hasV2Leverage(market)) + +export const hasV1Leverage = (market: LlamaMarketTemplate) => + market instanceof LendMarketTemplate ? market.leverage.hasLeverage() : market?.leverageZap !== zeroAddress + +export const hasV2Leverage = (market: MintMarketTemplate) => !!market?.leverageV2.hasLeverage() + +export const hasV1Deleverage = (market: LlamaMarketTemplate) => + market instanceof LendMarketTemplate ? market.leverage.hasLeverage() : market?.deleverageZap !== zeroAddress + +// hasV2Leverage works for deleverage as well +export const hasDeleverage = (market: LlamaMarketTemplate) => + hasV1Deleverage(market) || (market instanceof MintMarketTemplate && hasV2Leverage(market)) const getBorrowSymbol = (market: LlamaMarketTemplate) => market instanceof MintMarketTemplate ? CRVUSD.symbol : market.borrowed_token.symbol diff --git a/apps/main/src/llamalend/mutations/repay.mutation.ts b/apps/main/src/llamalend/mutations/repay.mutation.ts index 0cab2876b7..9b449022cf 100644 --- a/apps/main/src/llamalend/mutations/repay.mutation.ts +++ b/apps/main/src/llamalend/mutations/repay.mutation.ts @@ -15,7 +15,13 @@ import { t } from '@ui-kit/lib/i18n' import { rootKeys } from '@ui-kit/lib/model' import { type Decimal, waitForApproval } from '@ui-kit/utils' -type RepayMutation = { stateCollateral: Decimal; userCollateral: Decimal; userBorrowed: Decimal; isFull: boolean } +type RepayMutation = { + stateCollateral: Decimal + userCollateral: Decimal + userBorrowed: Decimal + isFull: boolean + leverageEnabled: boolean +} export type RepayOptions = { marketId: string | undefined @@ -25,10 +31,17 @@ export type RepayOptions = { userAddress: Address | undefined } -const approveRepay = async (market: LlamaMarketTemplate, { userCollateral, userBorrowed, isFull }: RepayMutation) => { +const approveRepay = async ( + market: LlamaMarketTemplate, + { userCollateral, userBorrowed, isFull, leverageEnabled }: RepayMutation, +) => { if (isFull) { return (await market.fullRepayApprove()) as Hex[] } + if (!leverageEnabled) { + console.assert(!+userCollateral, `userCollateral should be 0 for non-leverage repay`) + return (await market.repayApprove(userBorrowed)) as Hex[] + } if (market instanceof LendMarketTemplate) { return (await market.leverage.repayApprove(userCollateral, userBorrowed)) as Hex[] } @@ -39,22 +52,25 @@ const approveRepay = async (market: LlamaMarketTemplate, { userCollateral, userB return (await market.repayApprove(userBorrowed)) as Hex[] } -const repay = async (market: LlamaMarketTemplate, mutation: RepayMutation): Promise => { - const { stateCollateral, userCollateral, userBorrowed, isFull } = mutation +const repay = async ( + market: LlamaMarketTemplate, + { stateCollateral, userCollateral, userBorrowed, isFull, leverageEnabled }: RepayMutation, +): Promise => { if (isFull) { return (await market.fullRepay()) as Hex } - + if (!leverageEnabled) { + console.assert(!+userCollateral, `userCollateral should be 0 for non-leverage repay`) + return (await market.repay(userBorrowed)) as Hex + } if (market instanceof LendMarketTemplate) { await market.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed) return (await market.leverage.repay(stateCollateral, userCollateral, userBorrowed)) as Hex } - if (market.leverageV2.hasLeverage()) { await market.leverageV2.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed) return (await market.leverageV2.repay(stateCollateral, userCollateral, userBorrowed)) as Hex } - console.assert(!+stateCollateral, `stateCollateral should be 0 for non-leverage repay`) console.assert(!+userBorrowed, `userBorrowed should be 0 for non-leverage repay`) return (await market.deleverage.repay(userCollateral)) as Hex @@ -74,27 +90,12 @@ export const useRepayMutation = ({ marketId, mutationKey: [...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repay'] as const, mutationFn: async (mutation, { market }) => { - const { stateCollateral, userBorrowed, userCollateral, isFull } = mutation - await waitForApproval({ - isApproved: () => - fetchRepayIsApproved( - { - chainId, - marketId, - userAddress, - stateCollateral, - userCollateral, - userBorrowed, - isFull, - }, - { staleTime: 0 }, - ), - onApprove: () => approveRepay(market, mutation), + isApproved: async () => await fetchRepayIsApproved(mutation, { staleTime: 0 }), + onApprove: async () => await approveRepay(market, mutation), message: t`Approved repayment`, config, }) - return { hash: await repay(market, mutation) } }, validationSuite: repayFromCollateralIsFullValidationSuite, diff --git a/apps/main/src/llamalend/queries/create-loan/create-loan-approve-estimate-gas.query.ts b/apps/main/src/llamalend/queries/create-loan/create-loan-approve-estimate-gas.query.ts index 546a35c5e5..10bf85b39a 100644 --- a/apps/main/src/llamalend/queries/create-loan/create-loan-approve-estimate-gas.query.ts +++ b/apps/main/src/llamalend/queries/create-loan/create-loan-approve-estimate-gas.query.ts @@ -44,6 +44,7 @@ const { useQuery: useCreateLoanApproveEstimateGas } = queryFactory({ dependencies: (params) => [createLoanMaxReceiveKey(params)], }) +// todo: expand this to consider estimation after approval, see `useRepayEstimateGas` export const useCreateLoanEstimateGas = ( networks: NetworkDict, query: GasEstimateParams, diff --git a/apps/main/src/llamalend/queries/repay/repay-bands.query.ts b/apps/main/src/llamalend/queries/repay/repay-bands.query.ts index e4556e4b41..1cb4a51e2a 100644 --- a/apps/main/src/llamalend/queries/repay/repay-bands.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-bands.query.ts @@ -1,8 +1,7 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' export const { useQuery: useRepayBands } = queryFactory({ queryKey: ({ @@ -12,7 +11,7 @@ export const { useQuery: useRepayBands } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayBands', @@ -25,13 +24,17 @@ export const { useQuery: useRepayBands } = queryFactory({ stateCollateral, userCollateral, userBorrowed, - }: RepayFromCollateralQuery): Promise<[number, number]> => { - const market = getLlamaMarket(marketId) - return market instanceof LendMarketTemplate - ? await market.leverage.repayBands(stateCollateral, userCollateral, userBorrowed) - : market.leverageV2.hasLeverage() - ? await market.leverageV2.repayBands(stateCollateral, userCollateral, userBorrowed) - : await market.deleverage.repayBands(userCollateral) + }: RepayQuery): Promise<[number, number]> => { + const [type, impl, args] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return impl.repayBands(...args) + case 'deleverage': + return impl.repayBands(...args) + case 'unleveraged': + return impl.repayBands(...args) + } }, - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: false }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-estimate-gas.query.ts b/apps/main/src/llamalend/queries/repay/repay-estimate-gas.query.ts new file mode 100644 index 0000000000..c73dca10e4 --- /dev/null +++ b/apps/main/src/llamalend/queries/repay/repay-estimate-gas.query.ts @@ -0,0 +1,130 @@ +import { useEstimateGas } from '@/llamalend/hooks/useEstimateGas' +import { getLlamaMarket } from '@/llamalend/llama.utils' +import type { NetworkDict } from '@/llamalend/llamalend.types' +import { type RepayIsApprovedParams, useRepayIsApproved } from '@/llamalend/queries/repay/repay-is-approved.query' +import type { IChainId, TGas } from '@curvefi/llamalend-api/lib/interfaces' +import { queryFactory, rootKeys } from '@ui-kit/lib/model' +import { type RepayIsFullQuery } from '../validation/manage-loan.types' +import { repayFromCollateralIsFullValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' + +const { useQuery: useRepayLoanEstimateGas } = queryFactory({ + queryKey: ({ + chainId, + marketId, + stateCollateral = '0', + userCollateral = '0', + userBorrowed = '0', + userAddress, + isFull, + slippage, + }: RepayIsApprovedParams) => + [ + ...rootKeys.userMarket({ chainId, marketId, userAddress }), + 'estimateGas.repay', + { stateCollateral }, + { userCollateral }, + { userBorrowed }, + { isFull }, + { slippage }, + ] as const, + queryFn: async ({ + marketId, + stateCollateral, + userCollateral, + userBorrowed, + isFull, + userAddress, + slippage, + }: RepayIsFullQuery): Promise => { + const market = getLlamaMarket(marketId) + if (isFull) { + return await market.estimateGas.fullRepay(userAddress) + } + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.estimateGas.repay(stateCollateral, userCollateral, userBorrowed, +slippage) + case 'deleverage': + throw new Error('estimateGas.repay is not supported for deleverage repay') + case 'unleveraged': + return await impl.estimateGas.repay(userBorrowed) + } + }, + staleTime: '1m', + validationSuite: repayFromCollateralIsFullValidationSuite, +}) + +const { useQuery: useRepayLoanApproveEstimateGas } = queryFactory({ + queryKey: ({ + chainId, + marketId, + stateCollateral = '0', + userCollateral = '0', + userBorrowed = '0', + userAddress, + isFull, + }: RepayIsApprovedParams) => + [ + ...rootKeys.userMarket({ chainId, marketId, userAddress }), + 'estimateGas.repayApprove', + { stateCollateral }, + { userCollateral }, + { userBorrowed }, + { isFull }, + ] as const, + queryFn: async ({ + marketId, + stateCollateral, + userCollateral, + userBorrowed, + isFull, + userAddress, + }: RepayIsFullQuery): Promise => { + if (isFull) { + return await getLlamaMarket(marketId).estimateGas.fullRepayApprove(userAddress) + } + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.estimateGas.repayApprove(userCollateral, userBorrowed) + case 'deleverage': + throw new Error('estimateGas.repayApprove is not supported for deleverage repay') + case 'unleveraged': + return await impl.estimateGas.repayApprove(userBorrowed) + } + }, + staleTime: '1m', + validationSuite: repayFromCollateralIsFullValidationSuite, +}) + +export const useRepayEstimateGas = ( + networks: NetworkDict, + query: RepayIsApprovedParams, + enabled?: boolean, +) => { + const { chainId } = query + const { data: isApproved, isLoading: isApprovedLoading, error: isApprovedError } = useRepayIsApproved(query, enabled) + const { + data: approveEstimate, + isLoading: approveLoading, + error: approveError, + } = useRepayLoanApproveEstimateGas(query, enabled && !isApproved) + const { + data: repayEstimate, + isLoading: repayLoading, + error: repayError, + } = useRepayLoanEstimateGas(query, enabled && isApproved) + const { + data, + isLoading: conversionLoading, + error: estimateError, + } = useEstimateGas(networks, chainId, isApproved ? approveEstimate : repayEstimate, enabled) + return { + data, + isLoading: [isApprovedLoading, approveLoading, repayLoading, conversionLoading].some(Boolean), + error: [isApprovedError, approveError, repayError, estimateError].find(Boolean), + } +} diff --git a/apps/main/src/llamalend/queries/repay/repay-expected-borrowed.query.ts b/apps/main/src/llamalend/queries/repay/repay-expected-borrowed.query.ts index 8b957233e4..1d67ed490d 100644 --- a/apps/main/src/llamalend/queries/repay/repay-expected-borrowed.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-expected-borrowed.query.ts @@ -1,11 +1,10 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type Decimal } from '@ui-kit/utils' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { decimal, type Decimal } from '@ui-kit/utils' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation, getUserDebt } from './repay-query.helpers' -type RepayExpectedBorrowedResult = { +export type RepayExpectedBorrowedResult = { totalBorrowed: Decimal borrowedFromStateCollateral?: Decimal borrowedFromUserCollateral?: Decimal @@ -21,7 +20,7 @@ export const { useQuery: useRepayExpectedBorrowed } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayExpectedBorrowed', @@ -29,23 +28,23 @@ export const { useQuery: useRepayExpectedBorrowed } = queryFactory({ { userCollateral }, { userBorrowed }, ] as const, - queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayFromCollateralQuery) => { - // todo: investigate if this is OK when the user's position is not leveraged - const market = getLlamaMarket(marketId) - if (market instanceof LendMarketTemplate) { - const result = await market.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed) - return result as RepayExpectedBorrowedResult + queryFn: async ({ chainId, marketId, userAddress, stateCollateral, userCollateral, userBorrowed }: RepayQuery) => { + const [type, impl, args] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return (await impl.repayExpectedBorrowed(...args)) as RepayExpectedBorrowedResult + case 'deleverage': { + const { stablecoins, routeIdx } = await impl.repayStablecoins(...args) + return { totalBorrowed: stablecoins[routeIdx] as Decimal } + } + case 'unleveraged': + return { + // todo: double if this is correct or if we should use the `debt` field from userState + totalBorrowed: decimal(getUserDebt({ chainId, marketId, userAddress }) - +userBorrowed)!, + } } - if (market.leverageV2.hasLeverage()) { - const result = await market.leverageV2.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed) - return result as RepayExpectedBorrowedResult - } - - console.assert(!+stateCollateral, `Expected 0 stateCollateral for non-leverage market, got ${stateCollateral}`) - console.assert(!+userBorrowed, `Expected 0 userBorrowed for non-leverage market, got ${userBorrowed}`) - const { stablecoins, routeIdx } = await market.deleverage.repayStablecoins(userCollateral) - return { totalBorrowed: stablecoins[routeIdx] as Decimal } }, staleTime: '1m', - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: true }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-gas-estimate.query.ts b/apps/main/src/llamalend/queries/repay/repay-gas-estimate.query.ts index cd4c62be54..4f7b8c9a81 100644 --- a/apps/main/src/llamalend/queries/repay/repay-gas-estimate.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-gas-estimate.query.ts @@ -5,11 +5,12 @@ import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { type FieldsOf } from '@ui-kit/lib' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import type { RepayFromCollateralHealthQuery } from '../validation/manage-loan.types' +import type { RepayHealthQuery } from '../validation/manage-loan.types' import { repayFromCollateralIsFullValidationSuite } from '../validation/manage-loan.validation' import { repayIsFullQueryKey } from './repay-is-full.query' +import { getRepayImplementation } from './repay-query.helpers' -type RepayFromCollateralGasQuery = RepayFromCollateralHealthQuery +type RepayFromCollateralGasQuery = RepayHealthQuery type RepayFromCollateralGasParams = FieldsOf> const { useQuery: useRepayGasEstimate } = queryFactory({ @@ -38,19 +39,22 @@ const { useQuery: useRepayGasEstimate } = queryFactory({ isFull, userAddress, }: RepayFromCollateralGasQuery) => { - const market = getLlamaMarket(marketId) if (isFull) { + const market = getLlamaMarket(marketId) return market instanceof LendMarketTemplate ? await market.estimateGas.fullRepay(userAddress) : await market.fullRepayEstimateGas(userAddress) } - if (market instanceof LendMarketTemplate) { - return await market.leverage.estimateGas.repay(stateCollateral, userCollateral, userBorrowed) + const [type, impl, args] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.estimateGas.repay(...args) + case 'deleverage': + return await impl.estimateGas.repay(...args) + case 'unleveraged': + return await impl.estimateGas.repay(...args) } - if (market.leverageV2.hasLeverage()) { - return await market.leverageV2.estimateGas.repay(stateCollateral, userCollateral, userBorrowed) - } - return await market.deleverage.estimateGas.repay(userCollateral) }, validationSuite: repayFromCollateralIsFullValidationSuite, dependencies: ({ @@ -71,7 +75,7 @@ export const useRepayEstimateGas = ( const { chainId } = query const { data: estimate, isLoading: estimateLoading, error: estimateError } = useRepayGasEstimate(query, enabled) const { - data, + data = null, isLoading: conversionLoading, error: conversionError, } = useEstimateGas(networks, chainId, estimate, enabled) diff --git a/apps/main/src/llamalend/queries/repay/repay-health.query.ts b/apps/main/src/llamalend/queries/repay/repay-health.query.ts index e531003b5b..213b664414 100644 --- a/apps/main/src/llamalend/queries/repay/repay-health.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-health.query.ts @@ -1,14 +1,10 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' import type { Decimal } from '@ui-kit/utils' -import { - type RepayFromCollateralHealthQuery, - type RepayFromCollateralHealthParams, -} from '../validation/manage-loan.types' +import { type RepayHealthQuery, type RepayHealthParams } from '../validation/manage-loan.types' import { repayFromCollateralIsFullValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' -export const { getQueryOptions: getRepayHealthOptions } = queryFactory({ +export const { getQueryOptions: getRepayHealthOptions, useQuery: useRepayHealth } = queryFactory({ queryKey: ({ chainId, marketId, @@ -17,7 +13,7 @@ export const { getQueryOptions: getRepayHealthOptions } = queryFactory({ userBorrowed = '0', userAddress, isFull, - }: RepayFromCollateralHealthParams) => + }: RepayHealthParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayHealth', @@ -26,21 +22,17 @@ export const { getQueryOptions: getRepayHealthOptions } = queryFactory({ { userBorrowed }, { isFull }, ] as const, - queryFn: async ({ - marketId, - stateCollateral, - userCollateral, - userBorrowed, - isFull, - }: RepayFromCollateralHealthQuery) => { - const market = getLlamaMarket(marketId) - return ( - market instanceof LendMarketTemplate - ? await market.leverage.repayHealth(stateCollateral, userCollateral, userBorrowed, isFull) - : market.leverageV2.hasLeverage() - ? await market.leverageV2.repayHealth(stateCollateral, userCollateral, userBorrowed, isFull) - : await market.deleverage.repayHealth(userCollateral, isFull) - ) as Decimal + queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed, isFull }: RepayHealthQuery) => { + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return (await impl.repayHealth(stateCollateral, userCollateral, userBorrowed, isFull)) as Decimal + case 'deleverage': + return (await impl.repayHealth(userCollateral, isFull)) as Decimal + case 'unleveraged': + return '0' as Decimal + } }, validationSuite: repayFromCollateralIsFullValidationSuite, }) diff --git a/apps/main/src/llamalend/queries/repay/repay-is-approved.query.ts b/apps/main/src/llamalend/queries/repay/repay-is-approved.query.ts index ba2c5ae3a1..2c864a8991 100644 --- a/apps/main/src/llamalend/queries/repay/repay-is-approved.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-is-approved.query.ts @@ -1,13 +1,11 @@ import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' +import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { - type RepayFromCollateralIsFullParams, - type RepayFromCollateralIsFullQuery, -} from '../validation/manage-loan.types' +import { type RepayIsFullParams, type RepayIsFullQuery } from '../validation/manage-loan.types' import { repayFromCollateralIsFullValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' -type RepayIsApprovedParams = RepayFromCollateralIsFullParams & { leverageEnabled?: boolean } +export type RepayIsApprovedParams = RepayIsFullParams export const { useQuery: useRepayIsApproved, fetchQuery: fetchRepayIsApproved } = queryFactory({ queryKey: ({ @@ -18,7 +16,6 @@ export const { useQuery: useRepayIsApproved, fetchQuery: fetchRepayIsApproved } userBorrowed = '0', userAddress, isFull, - leverageEnabled, }: RepayIsApprovedParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), @@ -27,7 +24,6 @@ export const { useQuery: useRepayIsApproved, fetchQuery: fetchRepayIsApproved } { userCollateral }, { userBorrowed }, { isFull }, - { leverageEnabled }, ] as const, queryFn: async ({ marketId, @@ -36,17 +32,19 @@ export const { useQuery: useRepayIsApproved, fetchQuery: fetchRepayIsApproved } userBorrowed, isFull, userAddress, - leverageEnabled, - }: RepayFromCollateralIsFullQuery & { leverageEnabled?: boolean }): Promise => { - const market = getLlamaMarket(marketId) - if (isFull) return await market.fullRepayIsApproved(userAddress) - if (market instanceof LendMarketTemplate) return await market.leverage.repayIsApproved(userCollateral, userBorrowed) - if (leverageEnabled && market.leverageV2.hasLeverage()) { - return await market.leverageV2.repayIsApproved(userCollateral, userBorrowed) + }: RepayIsFullQuery): Promise => { + if (isFull) return await getLlamaMarket(marketId).fullRepayIsApproved(userAddress) + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.repayIsApproved(userCollateral, userBorrowed) + case 'deleverage': + console.warn('repayIsApproved is not supported for deleverage repay') + return true // todo: figure out approval for deleverage repay + case 'unleveraged': + return await impl.repayIsApproved(userBorrowed) } - console.assert(!+stateCollateral, `Expected 0 stateCollateral for non-leverage market, got ${stateCollateral}`) - console.assert(!+userCollateral, `Expected 0 userCollateral for non-leverage market, got ${userCollateral}`) - return await market.repayIsApproved(userCollateral) }, staleTime: '1m', validationSuite: repayFromCollateralIsFullValidationSuite, diff --git a/apps/main/src/llamalend/queries/repay/repay-is-available.query.ts b/apps/main/src/llamalend/queries/repay/repay-is-available.query.ts index 937661aa62..31d576c009 100644 --- a/apps/main/src/llamalend/queries/repay/repay-is-available.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-is-available.query.ts @@ -1,8 +1,7 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation, getUserDebt } from './repay-query.helpers' export const { useQuery: useRepayIsAvailable } = queryFactory({ queryKey: ({ @@ -12,7 +11,7 @@ export const { useQuery: useRepayIsAvailable } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayIsAvailable', @@ -21,19 +20,25 @@ export const { useQuery: useRepayIsAvailable } = queryFactory({ { userBorrowed }, ] as const, queryFn: async ({ + chainId, marketId, stateCollateral, userCollateral, userBorrowed, userAddress, - }: RepayFromCollateralQuery): Promise => { - const market = getLlamaMarket(marketId) - return market instanceof LendMarketTemplate - ? await market.leverage.repayIsAvailable(stateCollateral, userCollateral, userBorrowed, userAddress) - : market.leverageV2.hasLeverage() - ? await market.leverageV2.repayIsAvailable(stateCollateral, userCollateral, userBorrowed, userAddress) - : await market.deleverage.isAvailable(userCollateral, userAddress) + }: RepayQuery): Promise => { + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.repayIsAvailable(stateCollateral, userCollateral, userBorrowed, userAddress) + case 'deleverage': + return await impl.isAvailable(userCollateral, userAddress) + case 'unleveraged': { + return !!getUserDebt({ chainId, marketId, userAddress }) + } + } }, staleTime: '1m', - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: false }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-is-full.query.ts b/apps/main/src/llamalend/queries/repay/repay-is-full.query.ts index 03a680f24c..5e52b7724d 100644 --- a/apps/main/src/llamalend/queries/repay/repay-is-full.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-is-full.query.ts @@ -1,8 +1,7 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation, getUserDebt } from './repay-query.helpers' export const { useQuery: useRepayIsFull, queryKey: repayIsFullQueryKey } = queryFactory({ queryKey: ({ @@ -12,7 +11,7 @@ export const { useQuery: useRepayIsFull, queryKey: repayIsFullQueryKey } = query userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayIsFull', @@ -21,19 +20,25 @@ export const { useQuery: useRepayIsFull, queryKey: repayIsFullQueryKey } = query { userBorrowed }, ] as const, queryFn: async ({ + chainId, marketId, stateCollateral, userCollateral, userBorrowed, userAddress, - }: RepayFromCollateralQuery): Promise => { - const market = getLlamaMarket(marketId) - return market instanceof LendMarketTemplate - ? await market.leverage.repayIsFull(stateCollateral, userCollateral, userBorrowed, userAddress) - : market.leverageV2.hasLeverage() - ? await market.leverageV2.repayIsFull(stateCollateral, userCollateral, userBorrowed, userAddress) - : await market.deleverage.isFullRepayment(userCollateral, userAddress) + }: RepayQuery): Promise => { + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.repayIsFull(stateCollateral, userCollateral, userBorrowed, userAddress) + case 'deleverage': + return await impl.isFullRepayment(userCollateral, userAddress) + case 'unleveraged': { + return +userBorrowed >= getUserDebt({ chainId, marketId, userAddress }) + } + } }, staleTime: '1m', - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: false }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-price-impact.query.ts b/apps/main/src/llamalend/queries/repay/repay-price-impact.query.ts index dc1dd23e4c..8bd12747c2 100644 --- a/apps/main/src/llamalend/queries/repay/repay-price-impact.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-price-impact.query.ts @@ -1,8 +1,7 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' type RepayPriceImpactResult = number @@ -14,7 +13,7 @@ export const { useQuery: useRepayPriceImpact } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayPriceImpact', @@ -27,14 +26,18 @@ export const { useQuery: useRepayPriceImpact } = queryFactory({ stateCollateral, userCollateral, userBorrowed, - }: RepayFromCollateralQuery): Promise => { - const market = getLlamaMarket(marketId) - return market instanceof LendMarketTemplate - ? +(await market.leverage.repayPriceImpact(stateCollateral, userCollateral)) - : market.leverageV2.hasLeverage() - ? +(await market.leverageV2.repayPriceImpact(stateCollateral, userCollateral)) - : +(await market.deleverage.priceImpact(userCollateral)) + }: RepayQuery): Promise => { + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return +(await impl.repayPriceImpact(stateCollateral, userCollateral)) + case 'deleverage': + return +(await impl.priceImpact(userCollateral)) + case 'unleveraged': + return 0 + } }, staleTime: '1m', - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: true }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-prices.query.ts b/apps/main/src/llamalend/queries/repay/repay-prices.query.ts index 98c4f14aa3..7d1a2e436c 100644 --- a/apps/main/src/llamalend/queries/repay/repay-prices.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-prices.query.ts @@ -1,9 +1,8 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' import type { Decimal } from '@ui-kit/utils' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' export const { useQuery: useRepayPrices } = queryFactory({ queryKey: ({ @@ -13,7 +12,7 @@ export const { useQuery: useRepayPrices } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayPrices', @@ -21,15 +20,17 @@ export const { useQuery: useRepayPrices } = queryFactory({ { userCollateral }, { userBorrowed }, ] as const, - queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayFromCollateralQuery) => { - const market = getLlamaMarket(marketId) - return ( - market instanceof LendMarketTemplate - ? await market.leverage.repayPrices(stateCollateral, userCollateral, userBorrowed) - : market.leverageV2.hasLeverage() - ? await market.leverageV2.repayPrices(stateCollateral, userCollateral, userBorrowed) - : await market.deleverage.repayPrices(userCollateral) - ) as Decimal[] + queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayQuery) => { + const [type, impl, args] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return (await impl.repayPrices(...args)) as Decimal[] + case 'deleverage': + return (await impl.repayPrices(...args)) as Decimal[] + case 'unleveraged': + return (await impl.repayPrices(...args)) as Decimal[] + } }, - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: false }), }) diff --git a/apps/main/src/llamalend/queries/repay/repay-query.helpers.ts b/apps/main/src/llamalend/queries/repay/repay-query.helpers.ts new file mode 100644 index 0000000000..5273d607cc --- /dev/null +++ b/apps/main/src/llamalend/queries/repay/repay-query.helpers.ts @@ -0,0 +1,51 @@ +import { getLlamaMarket, hasDeleverage, hasLeverage, hasV2Leverage } from '@/llamalend/llama.utils' +import { getUserState } from '@/llamalend/queries/user-state.query' +import type { RepayQuery } from '@/llamalend/queries/validation/manage-loan.types' +import { MintMarketTemplate } from '@curvefi/llamalend-api/lib/mintMarkets' +import { type UserMarketQuery } from '@ui-kit/lib/model' + +/** + * Determines the appropriate repay implementation and its parameters based on the market type and leverage options. + * We will use V2 leverage if available, then leverage V1. Otherwise: + * - if deleverage is supported and userCollateral > 0, we use deleverage + * - otherwise, we use the unleveraged implementation. + */ +export function getRepayImplementation( + marketId: string, + { + stateCollateral, + userCollateral, + userBorrowed, + }: Pick, +) { + const market = getLlamaMarket(marketId) + + if (market instanceof MintMarketTemplate) { + if (hasV2Leverage(market)) { + return ['V2', market.leverageV2, [stateCollateral, userCollateral, userBorrowed]] as const + } + if (+userCollateral && hasDeleverage(market)) { + // use deleverage only if userCollateral > 0 & supported, otherwise fall back to unleveraged + if (+userBorrowed) throw new Error(`Invalid userBorrowed for deleverage: ${userBorrowed}`) + if (+stateCollateral) throw new Error(`Invalid stateCollateral for deleverage: ${stateCollateral}`) + return ['deleverage', market.deleverage, [userCollateral]] as const + } + } else if (hasLeverage(market)) { + // v1 leverage for mint markets is ignored, it doesn't support repay. Supported via deleverage above + return ['V1', market.leverage, [stateCollateral, userCollateral, userBorrowed]] as const + } + + if (+userCollateral) throw new Error(`Invalid userCollateral for deleverage: ${userCollateral}`) + if (+stateCollateral) throw new Error(`Invalid stateCollateral for deleverage: ${stateCollateral}`) + return ['unleveraged', market, [userBorrowed]] as const +} + +// todo: check if we should use `stablecoin` instead of `debt` +export const getUserDebt = ({ chainId, userAddress, marketId }: UserMarketQuery) => + +( + getUserState({ + chainId, + marketId, + userAddress, + })?.debt ?? 0 + ) diff --git a/apps/main/src/llamalend/queries/repay/repay-route-image.query.ts b/apps/main/src/llamalend/queries/repay/repay-route-image.query.ts index 9a35990c74..aa18da4d1f 100644 --- a/apps/main/src/llamalend/queries/repay/repay-route-image.query.ts +++ b/apps/main/src/llamalend/queries/repay/repay-route-image.query.ts @@ -1,8 +1,7 @@ -import { getLlamaMarket } from '@/llamalend/llama.utils' -import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets' import { queryFactory, rootKeys } from '@ui-kit/lib/model' -import { type RepayFromCollateralParams, type RepayFromCollateralQuery } from '../validation/manage-loan.types' -import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation' +import { type RepayParams, type RepayQuery } from '../validation/manage-loan.types' +import { repayValidationSuite } from '../validation/manage-loan.validation' +import { getRepayImplementation } from './repay-query.helpers' export const { useQuery: useRepayRouteImage } = queryFactory({ queryKey: ({ @@ -12,7 +11,7 @@ export const { useQuery: useRepayRouteImage } = queryFactory({ userCollateral = '0', userBorrowed = '0', userAddress, - }: RepayFromCollateralParams) => + }: RepayParams) => [ ...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repayRouteImage', @@ -20,16 +19,17 @@ export const { useQuery: useRepayRouteImage } = queryFactory({ { userCollateral }, { userBorrowed }, ] as const, - queryFn: async ({ marketId, stateCollateral, userCollateral }: RepayFromCollateralQuery) => { - const market = getLlamaMarket(marketId) - if (market instanceof LendMarketTemplate) { - return await market.leverage.repayRouteImage(stateCollateral, userCollateral) + queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayQuery) => { + const [type, impl] = getRepayImplementation(marketId, { userCollateral, stateCollateral, userBorrowed }) + switch (type) { + case 'V1': + case 'V2': + return await impl.repayRouteImage(stateCollateral, userCollateral) + case 'deleverage': + case 'unleveraged': + throw new Error('repayRouteImage is not supported for deleverage or unleveraged repay') } - if (market.leverageV2.hasLeverage()) { - return await market.leverageV2.repayRouteImage(stateCollateral, userCollateral) - } - return null }, staleTime: '1m', - validationSuite: repayFromCollateralValidationSuite, + validationSuite: repayValidationSuite({ leverageRequired: true }), }) diff --git a/apps/main/src/llamalend/queries/user-health.query.ts b/apps/main/src/llamalend/queries/user-health.query.ts index 325437bcc2..fed3abc270 100644 --- a/apps/main/src/llamalend/queries/user-health.query.ts +++ b/apps/main/src/llamalend/queries/user-health.query.ts @@ -3,7 +3,7 @@ import { queryFactory, rootKeys, type UserMarketParams, type UserMarketQuery } f import { userMarketValidationSuite } from '@ui-kit/lib/model/query/user-market-validation' import { createValidationSuite } from '@ui-kit/lib/validation' import type { Decimal } from '@ui-kit/utils' -import { validateIsFull } from './validation/borrow-fields.validation' +import { validateBoolean } from './validation/borrow-fields.validation' type UserHealthParams = UserMarketParams & { isFull: boolean } type UserHealthQuery = UserMarketQuery & { isFull: boolean } @@ -19,6 +19,6 @@ export const { getQueryOptions: getUserHealthOptions, invalidate: invalidateUser (await getLlamaMarket(marketId).userHealth(isFull, userAddress)) as Decimal, validationSuite: createValidationSuite(({ userAddress, isFull, marketId, chainId }: UserHealthParams) => { userMarketValidationSuite({ userAddress, marketId, chainId }) - validateIsFull(isFull) + validateBoolean(isFull) }), }) diff --git a/apps/main/src/llamalend/queries/user-state.query.ts b/apps/main/src/llamalend/queries/user-state.query.ts index e747922f09..45c47ab0a7 100644 --- a/apps/main/src/llamalend/queries/user-state.query.ts +++ b/apps/main/src/llamalend/queries/user-state.query.ts @@ -3,7 +3,11 @@ import { queryFactory, rootKeys, type UserMarketParams, type UserMarketQuery } f import { userMarketValidationSuite } from '@ui-kit/lib/model/query/user-market-validation' import type { Decimal } from '@ui-kit/utils' -export const { useQuery: useUserState, invalidate: invalidateUserState } = queryFactory({ +export const { + useQuery: useUserState, + invalidate: invalidateUserState, + getQueryData: getUserState, +} = queryFactory({ queryKey: (params: UserMarketParams) => [...rootKeys.userMarket(params), 'market-user-state'] as const, queryFn: async ({ marketId, userAddress }: UserMarketQuery) => { const market = getLlamaMarket(marketId) diff --git a/apps/main/src/llamalend/queries/validation/borrow-fields.validation.ts b/apps/main/src/llamalend/queries/validation/borrow-fields.validation.ts index 55348eba37..a3b1668e3d 100644 --- a/apps/main/src/llamalend/queries/validation/borrow-fields.validation.ts +++ b/apps/main/src/llamalend/queries/validation/borrow-fields.validation.ts @@ -1,5 +1,6 @@ import { enforce, test, skipWhen } from 'vest' import { BORROW_PRESET_RANGES } from '@/llamalend/constants' +import { getLlamaMarket, hasLeverage } from '@/llamalend/llama.utils' import { Decimal } from '@ui-kit/utils' export const validateUserBorrowed = (userBorrowed: Decimal | null | undefined) => { @@ -60,6 +61,15 @@ export const validateLeverageEnabled = (leverageEnabled: boolean | undefined | n }) } +export const validateLeverageSupported = (marketId: string | null | undefined, leverageRequired: boolean) => { + skipWhen(!leverageRequired || !marketId, () => { + test('marketId', 'Market does not support leverage', () => { + const market = getLlamaMarket(marketId!) + enforce(hasLeverage(market)).isTruthy() + }) + }) +} + export const validateMaxCollateral = ( userCollateral: Decimal | undefined | null, maxCollateral: Decimal | undefined | null, @@ -71,7 +81,7 @@ export const validateMaxCollateral = ( }) } -export const validateIsFull = (value: boolean | undefined | null) => { +export const validateBoolean = (value: boolean | undefined | null) => { test('root', 'Form is not completely filled out', () => { enforce(value).isBoolean() }) diff --git a/apps/main/src/llamalend/queries/validation/manage-loan.types.ts b/apps/main/src/llamalend/queries/validation/manage-loan.types.ts index ca7a7b613e..7d86767d7c 100644 --- a/apps/main/src/llamalend/queries/validation/manage-loan.types.ts +++ b/apps/main/src/llamalend/queries/validation/manage-loan.types.ts @@ -9,16 +9,17 @@ type HealthQuery = { isFull: boolean } export type CollateralHealthQuery = CollateralQuery & HealthQuery -export type RepayFromCollateralQuery = CollateralQuery & { +export type RepayQuery = CollateralQuery & { stateCollateral: Decimal userBorrowed: Decimal + slippage: Decimal } -export type RepayFromCollateralHealthQuery = RepayFromCollateralQuery & HealthQuery -export type RepayFromCollateralIsFullQuery = RepayFromCollateralQuery & HealthQuery +export type RepayHealthQuery = RepayQuery & HealthQuery +export type RepayIsFullQuery = RepayQuery & HealthQuery -export type RepayFromCollateralParams = FieldsOf> -export type RepayFromCollateralHealthParams = FieldsOf> -export type RepayFromCollateralIsFullParams = FieldsOf> +export type RepayParams = FieldsOf> +export type RepayHealthParams = FieldsOf> +export type RepayIsFullParams = FieldsOf> export type CollateralParams = FieldsOf> export type CollateralHealthParams = FieldsOf> diff --git a/apps/main/src/llamalend/queries/validation/manage-loan.validation.ts b/apps/main/src/llamalend/queries/validation/manage-loan.validation.ts index d148b514ab..acbc9f7792 100644 --- a/apps/main/src/llamalend/queries/validation/manage-loan.validation.ts +++ b/apps/main/src/llamalend/queries/validation/manage-loan.validation.ts @@ -1,14 +1,17 @@ -import { group } from 'vest' +import BigNumber from 'bignumber.js' +import { enforce, group, skipWhen, test } from 'vest' +import { getRepayImplementation } from '@/llamalend/queries/repay/repay-query.helpers' import { - validateIsFull, - validateUserBorrowed, + validateBoolean, + validateLeverageSupported, + validateSlippage, validateUserCollateral, } from '@/llamalend/queries/validation/borrow-fields.validation' import type { CollateralHealthParams, CollateralParams, - RepayFromCollateralIsFullParams, - RepayFromCollateralParams, + RepayIsFullParams, + RepayParams, } from '@/llamalend/queries/validation/manage-loan.types' import { createValidationSuite, type FieldsOf } from '@ui-kit/lib' import { chainValidationGroup } from '@ui-kit/lib/model/query/chain-validation' @@ -19,12 +22,54 @@ import type { Decimal } from '@ui-kit/utils' export type CollateralForm = FieldsOf<{ userCollateral: Decimal }> -export type RepayForm = FieldsOf<{ - stateCollateral: Decimal - userCollateral: Decimal - userBorrowed: Decimal - isFull: boolean -}> +export type RepayForm = { + stateCollateral: Decimal | undefined + userCollateral: Decimal | undefined + userBorrowed: Decimal | undefined + isFull: boolean | undefined + slippage: Decimal + withdrawEnabled: boolean + leverageEnabled: boolean +} + +const validateRepayField = (field: 'stateCollateral' | 'userCollateral', value: Decimal | null | undefined) => + test(field, `Collateral amount must be a non-negative number`, () => { + if (value == null) return + enforce(value).isNumeric().gte(0) + }) + +const validateRepayBorrowedField = (userBorrowed: Decimal | null | undefined) => + test('userBorrowed', 'Borrow amount must be a non-negative number', () => { + if (userBorrowed == null) return + enforce(userBorrowed).isNumeric().gte(0) + }) + +const validateRepayHasValue = ( + stateCollateral: Decimal | null | undefined, + userCollateral: Decimal | null | undefined, + userBorrowed: Decimal | null | undefined, +) => + test('root', 'Enter an amount to repay', () => { + const total = new BigNumber(stateCollateral ?? 0).plus(userCollateral ?? 0).plus(userBorrowed ?? 0) + + enforce(total.gt(0)).isTruthy() + }) + +const validateRepayFieldsForMarket = ( + marketId: string | null | undefined, + stateCollateral: Decimal | null | undefined, + userCollateral: Decimal | null | undefined, + userBorrowed: Decimal | null | undefined, +) => { + skipWhen(!marketId, () => { + // Get the implementation to validate fields according to market capabilities. Default to 0 just like the queries + getRepayImplementation(marketId!, { + stateCollateral: stateCollateral ?? '0', + userCollateral: userCollateral ?? '0', + userBorrowed: userBorrowed ?? '0', + }) + }) +} export const collateralValidationGroup = ({ chainId, userCollateral, marketId, userAddress }: CollateralParams) => group('chainValidation', () => { @@ -43,38 +88,52 @@ export const collateralFormValidationSuite = createValidationSuite((params: Coll export const collateralHealthValidationSuite = createValidationSuite(({ isFull, ...rest }: CollateralHealthParams) => { collateralValidationGroup(rest) - validateIsFull(isFull) + validateBoolean(isFull) }) -export const repayFromCollateralValidationGroup = ({ - chainId, - stateCollateral, - userCollateral, - userBorrowed, - userAddress, -}: RepayFromCollateralParams) => { +export const repayValidationGroup = ( + { chainId, marketId, stateCollateral, userCollateral, userBorrowed, userAddress, slippage }: RepayParams, + { leverageRequired = false }: { leverageRequired?: boolean } = {}, +) => { chainValidationGroup({ chainId }) llamaApiValidationGroup({ chainId }) userAddressValidationGroup({ userAddress }) - validateUserCollateral(userCollateral) - validateUserCollateral(stateCollateral) - validateUserBorrowed(userBorrowed) + validateRepayField('userCollateral', userCollateral) + validateRepayField('stateCollateral', stateCollateral) + validateRepayBorrowedField(userBorrowed) + validateRepayHasValue(stateCollateral, userCollateral, userBorrowed) + validateRepayFieldsForMarket(marketId, stateCollateral, userCollateral, userBorrowed) + validateSlippage(slippage) + validateLeverageSupported(marketId, leverageRequired) } -export const repayFromCollateralValidationSuite = createValidationSuite((params: RepayFromCollateralParams) => - repayFromCollateralValidationGroup(params), -) +export const repayValidationSuite = ({ leverageRequired }: { leverageRequired: boolean }) => + createValidationSuite((params: RepayParams) => repayValidationGroup(params, { leverageRequired })) -export const repayFormValidationSuite = createValidationSuite((params: RepayForm) => { - validateUserCollateral(params.userCollateral) - validateUserCollateral(params.stateCollateral) - validateUserBorrowed(params.userBorrowed) - validateIsFull(params.isFull) -}) +export const repayFormValidationSuite = createValidationSuite( + ({ + isFull, + stateCollateral, + userCollateral, + userBorrowed, + withdrawEnabled, + leverageEnabled, + slippage, + }: RepayForm) => { + validateRepayField('userCollateral', userCollateral) + validateRepayField('stateCollateral', stateCollateral) + validateRepayBorrowedField(userBorrowed) + validateRepayHasValue(stateCollateral, userCollateral, userBorrowed) + validateBoolean(isFull) + validateBoolean(leverageEnabled) + validateBoolean(withdrawEnabled) + validateSlippage(slippage) + }, +) export const repayFromCollateralIsFullValidationSuite = createValidationSuite( - ({ isFull, ...params }: RepayFromCollateralIsFullParams) => { - repayFromCollateralValidationGroup(params) - group('isFull', () => validateIsFull(isFull)) + ({ isFull, ...params }: RepayIsFullParams) => { + repayValidationGroup(params) + group('isFull', () => validateBoolean(isFull)) }, ) diff --git a/apps/main/src/llamalend/widgets/manage-loan/LoanFormWrapper.tsx b/apps/main/src/llamalend/widgets/manage-loan/LoanFormWrapper.tsx index ea9637f8b2..5dfb01d1d0 100644 --- a/apps/main/src/llamalend/widgets/manage-loan/LoanFormWrapper.tsx +++ b/apps/main/src/llamalend/widgets/manage-loan/LoanFormWrapper.tsx @@ -2,7 +2,6 @@ import type { FormEventHandler, ReactNode } from 'react' import { FormProvider } from 'react-hook-form' import type { FieldValues, FormProviderProps } from 'react-hook-form' import Stack from '@mui/material/Stack' -import { AppFormContentWrapper } from '@ui/AppForm' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' const { Spacing } = SizesAndSpaces @@ -24,10 +23,25 @@ export const LoanFormWrapper =
- t.design.Layer[1].Fill }}> - - {children} - + t.design.Layer[1].Fill, + // + // align-items: flex-start; + // display: grid; + // grid-row-gap: var(--spacing-3); + // padding: var(--spacing-3); + // min-height: 17.125rem; + // width: ${MaxWidth.actionCard}; + // max-width: ${MaxWidth.actionCard}; + // // let the action card take the full width below the tablet breakpoint + // @media (max-width: ${basicMuiTheme.breakpoints.values.tablet}px) { + // width: 100%; + // max-width: 100%; + // } + }} + > + {children} {infoAccordion} diff --git a/apps/main/src/llamalend/widgets/manage-loan/LoanInfoAccordion.tsx b/apps/main/src/llamalend/widgets/manage-loan/LoanInfoAccordion.tsx index 46edfe8fd9..616ed67cd2 100644 --- a/apps/main/src/llamalend/widgets/manage-loan/LoanInfoAccordion.tsx +++ b/apps/main/src/llamalend/widgets/manage-loan/LoanInfoAccordion.tsx @@ -24,7 +24,7 @@ export type LoanLeverageMaxReceive = { maxLeverage?: Decimal } -type LoanInfoAccordionProps = { +export type LoanInfoAccordionProps = { isOpen: boolean toggle: () => void range?: number @@ -37,7 +37,7 @@ type LoanInfoAccordionProps = { loanToValue: Query prevLoanToValue?: Query gas: Query - debt?: Query & { tokenSymbol: string } + debt?: Query & { tokenSymbol: string | undefined } prevDebt?: Query leverage?: LoanLeverageActionInfoProps & { enabled: boolean } } diff --git a/apps/main/src/llamalend/widgets/manage-loan/LoanLeverageActionInfo.tsx b/apps/main/src/llamalend/widgets/manage-loan/LoanLeverageActionInfo.tsx index c0e8c68012..52bc8ff763 100644 --- a/apps/main/src/llamalend/widgets/manage-loan/LoanLeverageActionInfo.tsx +++ b/apps/main/src/llamalend/widgets/manage-loan/LoanLeverageActionInfo.tsx @@ -1,4 +1,5 @@ import { notFalsy } from 'router-api/src/router.utils' +import type { RepayExpectedBorrowedResult } from '@/llamalend/queries/repay/repay-expected-borrowed.query' import { t } from '@ui-kit/lib/i18n' import ActionInfo from '@ui-kit/shared/ui/ActionInfo' import type { Query } from '@ui-kit/types/util' @@ -7,8 +8,9 @@ import { SlippageToleranceActionInfo } from '@ui-kit/widgets/SlippageSettings' import type { LoanLeverageExpectedCollateral, LoanLeverageMaxReceive } from './LoanInfoAccordion' export type LoanLeverageActionInfoProps = { - expectedCollateral: Query - maxReceive: Query + expectedCollateral?: Query + expectedBorrowed?: Query + maxReceive?: Query priceImpact: Query slippage: Decimal onSlippageChange: (newSlippage: Decimal) => void @@ -28,46 +30,41 @@ export const LoanLeverageActionInfo = ({ onSlippageChange, collateralSymbol, }: LoanLeverageActionInfoProps) => { - const { - data: expectedCollateralData, - isLoading: expectedCollateralLoading, - error: expectedCollateralError, - } = expectedCollateral - const { data: maxReceiveData, isLoading: maxReceiveLoading, error: maxReceiveError } = maxReceive - const { data: priceImpactPercent, isLoading: priceImpactPercentLoading, error: priceImpactPercentError } = priceImpact - - const { totalCollateral, leverage } = expectedCollateralData ?? {} - const { avgPrice, maxLeverage } = maxReceiveData ?? {} - - const isHighImpact = priceImpactPercent != null && priceImpactPercent > +slippage + const isHighImpact = priceImpact.data != null && priceImpact.data > +slippage return ( <> - - - + {expectedCollateral && maxReceive && ( + q.error)?.error} + loading={[expectedCollateral, maxReceive].some((q) => q.isLoading)} + /> + )} + {expectedCollateral && ( + + )} + {maxReceive && ( + + )} diff --git a/packages/curve-ui-kit/src/types/util.ts b/packages/curve-ui-kit/src/types/util.ts index 17eb674967..771c45222e 100644 --- a/packages/curve-ui-kit/src/types/util.ts +++ b/packages/curve-ui-kit/src/types/util.ts @@ -1,3 +1,5 @@ +import type { UseQueryResult } from '@tanstack/react-query' + /** * Creates a deep partial type that makes all properties optional recursively, * while preserving function types as-is @@ -38,3 +40,14 @@ export type MakeOptional = Omit & Partial * @template T - The type of the data returned by the query. */ export type Query = { data: T | undefined; isLoading: boolean; error: Error | null | undefined } + +/** + * Helper to extract only the relevant fields from a UseQueryResult into the Query type. + * This is necessary because passing UseQueryResult to any react component will crash the rendering due to + * react trying to serialize the react-query proxy object. + */ +export const q = ({ data, isLoading, error }: UseQueryResult): Query => ({ + data, + isLoading, + error, +})