diff --git a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx index 144b1032bc..f1be73eb75 100644 --- a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx +++ b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/index.tsx @@ -9,13 +9,14 @@ import DialogHealthLeverageWarning from '@/loan/components/PageLoanCreate/LoanFo import type { FormStatus, FormValues, PageLoanCreateProps, StepKey } from '@/loan/components/PageLoanCreate/types' import { StyledInpChip } from '@/loan/components/PageLoanManage/styles' import type { FormEstGas } from '@/loan/components/PageLoanManage/types' -import { DEFAULT_FORM_EST_GAS, DEFAULT_HEALTH_MODE, hasDeleverage } from '@/loan/components/PageLoanManage/utils' +import { DEFAULT_FORM_EST_GAS, DEFAULT_HEALTH_MODE } from '@/loan/components/PageLoanManage/utils' import { DEFAULT_WALLET_BALANCES } from '@/loan/constants' import networks from '@/loan/networks' import { DEFAULT_FORM_STATUS } from '@/loan/store/createLoanCollateralIncreaseSlice' import useStore from '@/loan/store/useStore' import { CollateralAlert, LlamaApi, Llamma } from '@/loan/types/loan.types' import { curveProps } from '@/loan/utils/helpers' +import { hasV1Deleverage } from '@/loan/utils/leverage' import { getStepStatus, getTokenName } from '@/loan/utils/utilsLoan' import { getLoanManagePathname } from '@/loan/utils/utilsRouter' import Accordion from '@ui/Accordion' @@ -437,7 +438,7 @@ const LoanCreate = ({

{t`You can leverage your collateral up to 9x. This has the effect of repeat trading crvUSD to collateral and depositing to maximize your collateral position. Essentially, all borrowed crvUSD is utilized to purchase more collateral.`}

{t`Be careful, if the collateral price dips, you would need to repay the entire amount to reclaim your initial position.`}

- {!hasDeleverage(llamma) && ( + {!hasV1Deleverage(llamma) && (

{t`WARNING: The corresponding deleverage button is also not yet available.`}

)}
diff --git a/apps/main/src/loan/components/PageLoanCreate/Page.tsx b/apps/main/src/loan/components/PageLoanCreate/Page.tsx index a4362a318b..786f58f003 100644 --- a/apps/main/src/loan/components/PageLoanCreate/Page.tsx +++ b/apps/main/src/loan/components/PageLoanCreate/Page.tsx @@ -11,12 +11,12 @@ import { DetailPageStack } from '@/llamalend/widgets/DetailPageStack' 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 networks from '@/loan/networks' import useStore from '@/loan/store/useStore' import { type CollateralUrlParams, type LlamaApi, Llamma } from '@/loan/types/loan.types' +import { hasV1Leverage } from '@/loan/utils/leverage' import { getTokenName } from '@/loan/utils/utilsLoan' import { getLoanCreatePathname, @@ -136,7 +136,7 @@ const Page = () => { // redirect if form is leverage but no leverage option useEffect(() => { - if (market && rFormType === 'leverage' && !hasLeverage(market)) { + if (market && rFormType === 'leverage' && !hasV1Leverage(market)) { push(getLoanCreatePathname(params, market.id)) } }, [loaded, rFormType, market, push, params]) diff --git a/apps/main/src/loan/components/PageLoanCreate/index.tsx b/apps/main/src/loan/components/PageLoanCreate/index.tsx index 6875ae7edc..295c58e655 100644 --- a/apps/main/src/loan/components/PageLoanCreate/index.tsx +++ b/apps/main/src/loan/components/PageLoanCreate/index.tsx @@ -4,11 +4,12 @@ import type { OnBorrowFormUpdate } from '@/llamalend/features/borrow/types' import type { CreateLoanOptions } from '@/llamalend/mutations/create-loan.mutation' import LoanFormCreate from '@/loan/components/PageLoanCreate/LoanFormCreate' import type { FormType, FormValues, PageLoanCreateProps } from '@/loan/components/PageLoanCreate/types' -import { DEFAULT_FORM_VALUES, hasLeverage } from '@/loan/components/PageLoanCreate/utils' +import { DEFAULT_FORM_VALUES } from '@/loan/components/PageLoanCreate/utils' import useCollateralAlert from '@/loan/hooks/useCollateralAlert' import networks from '@/loan/networks' import useStore from '@/loan/store/useStore' import { LlamaApi, Llamma } from '@/loan/types/loan.types' +import { hasV1Leverage } from '@/loan/utils/leverage' import { getLoanCreatePathname, getLoanManagePathname } from '@/loan/utils/utilsRouter' import Stack from '@mui/material/Stack' import { AppFormContentWrapper } from '@ui/AppForm' @@ -68,7 +69,7 @@ const LoanCreate = ({ [{ value: 'create' as const, label: t`Borrow` }] : [ { value: 'create' as const, label: t`Create Loan` }, - ...(hasLeverage(llamma) ? [{ value: 'leverage' as const, label: t`Leverage` }] : []), + ...(hasV1Leverage(llamma) ? [{ value: 'leverage' as const, label: t`Leverage` }] : []), ], [llamma, isBorrowUnifiedForm], ) diff --git a/apps/main/src/loan/components/PageLoanCreate/utils.ts b/apps/main/src/loan/components/PageLoanCreate/utils.ts index e56725d2eb..f16621e9a8 100644 --- a/apps/main/src/loan/components/PageLoanCreate/utils.ts +++ b/apps/main/src/loan/components/PageLoanCreate/utils.ts @@ -1,6 +1,5 @@ import type { FormDetailInfoLeverage, FormStatus, FormValues } from '@/loan/components/PageLoanCreate/types' import { DEFAULT_FORM_STATUS as FORM_STATUS } from '@/loan/components/PageLoanManage/utils' -import { Llamma } from '@/loan/types/loan.types' export const DEFAULT_DETAIL_INFO_LEVERAGE: FormDetailInfoLeverage = { collateral: '', @@ -30,7 +29,3 @@ export const DEFAULT_FORM_VALUES: FormValues = { debtError: '', n: null, } - -export function hasLeverage(llamma: Llamma | null) { - return !!llamma && llamma?.leverageZap !== '0x0000000000000000000000000000000000000000' -} diff --git a/apps/main/src/loan/components/PageLoanManage/LoanDeleverage/index.tsx b/apps/main/src/loan/components/PageLoanManage/LoanDeleverage/index.tsx index 77911b41f9..0ee68aea58 100644 --- a/apps/main/src/loan/components/PageLoanManage/LoanDeleverage/index.tsx +++ b/apps/main/src/loan/components/PageLoanManage/LoanDeleverage/index.tsx @@ -16,17 +16,13 @@ import type { FormDetailInfo, FormStatus, FormValues } from '@/loan/components/P import { DEFAULT_FORM_VALUES } from '@/loan/components/PageLoanManage/LoanDeleverage/utils' import { StyledDetailInfoWrapper, StyledInpChip } from '@/loan/components/PageLoanManage/styles' import type { PageLoanManageProps } from '@/loan/components/PageLoanManage/types' -import { - DEFAULT_DETAIL_INFO, - DEFAULT_FORM_EST_GAS, - DEFAULT_HEALTH_MODE, - hasDeleverage, -} from '@/loan/components/PageLoanManage/utils' +import { DEFAULT_DETAIL_INFO, DEFAULT_FORM_EST_GAS, DEFAULT_HEALTH_MODE } from '@/loan/components/PageLoanManage/utils' import { useUserLoanDetails } from '@/loan/hooks/useUserLoanDetails' import networks from '@/loan/networks' import useStore from '@/loan/store/useStore' import { LlamaApi, Llamma } from '@/loan/types/loan.types' import { curveProps } from '@/loan/utils/helpers' +import { hasDeleverage } from '@/loan/utils/leverage' import { getStepStatus, getTokenName } from '@/loan/utils/utilsLoan' import { getCollateralListPathname } from '@/loan/utils/utilsRouter' import AlertBox from '@ui/AlertBox' diff --git a/apps/main/src/loan/components/PageLoanManage/Page.tsx b/apps/main/src/loan/components/PageLoanManage/Page.tsx index 9772e60742..831c8073ae 100644 --- a/apps/main/src/loan/components/PageLoanManage/Page.tsx +++ b/apps/main/src/loan/components/PageLoanManage/Page.tsx @@ -13,7 +13,6 @@ import ChartOhlcWrapper from '@/loan/components/ChartOhlcWrapper' 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' @@ -21,6 +20,7 @@ import { useUserLoanDetails } from '@/loan/hooks/useUserLoanDetails' import networks from '@/loan/networks' import useStore from '@/loan/store/useStore' import type { CollateralUrlParams } from '@/loan/types/loan.types' +import { hasDeleverage } from '@/loan/utils/leverage' import { getLoanCreatePathname, parseCollateralParams, useChainId } from '@/loan/utils/utilsRouter' import { isChain } from '@curvefi/prices-api' import Stack from '@mui/material/Stack' diff --git a/apps/main/src/loan/components/PageLoanManage/index.tsx b/apps/main/src/loan/components/PageLoanManage/index.tsx index c0b16f0d3e..e29d4768f5 100644 --- a/apps/main/src/loan/components/PageLoanManage/index.tsx +++ b/apps/main/src/loan/components/PageLoanManage/index.tsx @@ -11,7 +11,7 @@ import type { LoanFormType, PageLoanManageProps, } from '@/loan/components/PageLoanManage/types' -import { hasDeleverage } from '@/loan/components/PageLoanManage/utils' +import { hasDeleverage } from '@/loan/utils/leverage' import { getLoanManagePathname } from '@/loan/utils/utilsRouter' import Stack from '@mui/material/Stack' import { AppFormContentWrapper } from '@ui/AppForm' diff --git a/apps/main/src/loan/components/PageLoanManage/utils.ts b/apps/main/src/loan/components/PageLoanManage/utils.ts index 7de4b8dd15..d1f23de0f0 100644 --- a/apps/main/src/loan/components/PageLoanManage/utils.ts +++ b/apps/main/src/loan/components/PageLoanManage/utils.ts @@ -1,6 +1,5 @@ -import { zeroAddress } from 'viem' import { FormDetailInfo, FormEstGas, FormStatus } from '@/loan/components/PageLoanManage/types' -import { Llamma, HealthMode, UserWalletBalances } from '@/loan/types/loan.types' +import { HealthMode, UserWalletBalances } from '@/loan/types/loan.types' export const DEFAULT_HEALTH_MODE: HealthMode = { percent: '', @@ -36,7 +35,3 @@ export const DEFAULT_USER_WALLET_BALANCES: UserWalletBalances = { stablecoin: '0', error: '', } - -export function hasDeleverage(llamma: Llamma | null) { - return !!llamma && llamma?.deleverageZap !== zeroAddress -} diff --git a/apps/main/src/loan/constants.ts b/apps/main/src/loan/constants.ts index 897c5fd1a8..02ad04f0e9 100644 --- a/apps/main/src/loan/constants.ts +++ b/apps/main/src/loan/constants.ts @@ -37,3 +37,11 @@ export const DEFAULT_WALLET_BALANCES: UserWalletBalances = { collateral: '0', error: '', } + +export enum RouteAggregator { + Odos = 'odos', +} + +export const ROUTE_AGGREGATOR_LABELS: Record = { + [RouteAggregator.Odos]: 'Odos', +} diff --git a/apps/main/src/loan/lib/apiCrvusd.ts b/apps/main/src/loan/lib/apiCrvusd.ts index 800e3134a9..5edd8df6de 100644 --- a/apps/main/src/loan/lib/apiCrvusd.ts +++ b/apps/main/src/loan/lib/apiCrvusd.ts @@ -4,6 +4,7 @@ import networks from '@/loan/networks' import type { LiqRange, MaxRecvLeverage, Provider } from '@/loan/store/types' import { ChainId, LlamaApi, Llamma, UserLoanDetails } from '@/loan/types/loan.types' import { fulfilledValue, getErrorMessage, log } from '@/loan/utils/helpers' +import { hasV2Leverage } from '@/loan/utils/leverage' import { getChartBandBalancesData, getIsUserCloseToLiquidation, @@ -14,6 +15,8 @@ import { } from '@/loan/utils/utilsCurvejs' import type { TGas } from '@curvefi/llamalend-api/lib/interfaces' import { waitForTransaction, waitForTransactions } from '@ui-kit/lib/ethers' +import { ROUTE_AGGREGATOR_LABELS, RouteAggregator } from '../constants' +import { getLeverageV2RepayArgs, isHigherThanMaxSlippage } from '../utils/utilsLoan' export const network = { 1: { @@ -953,9 +956,26 @@ const loanDeleverage = { estGas: async (activeKey: string, llamma: Llamma, collateral: string, maxSlippage: string) => { log('estGas', llamma.collateralSymbol, collateral, maxSlippage) const resp = { activeKey, estimatedGas: initialGas, error: '' } + const slippage = Number(maxSlippage) || 0 try { - resp.estimatedGas = await llamma.deleverage.estimateGas.repay(collateral, +maxSlippage) + if (hasV2Leverage(llamma)) { + const repayArgs = getLeverageV2RepayArgs(collateral) + await llamma.leverageV2.repayExpectedBorrowed( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + slippage, + ) + resp.estimatedGas = await llamma.leverageV2.estimateGas.repay( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + slippage, + ) + } else { + resp.estimatedGas = await llamma.deleverage.estimateGas.repay(collateral, slippage) + } return resp } catch (error) { console.error(error) @@ -988,36 +1008,110 @@ const loanDeleverage = { } try { - // check if deleverage is available - const deleverageCollateral = +userState.collateral > 0 ? userState.collateral : collateral - if (+deleverageCollateral > 0) { - resp.isAvailable = await llamma.deleverage.isAvailable(deleverageCollateral) - } + const slippage = Number(maxSlippage) || 0 + if (hasV2Leverage(llamma)) { + const repayArgs = getLeverageV2RepayArgs(collateral) + const availabilityCollateral = +userState.collateral > 0 ? userState.collateral : repayArgs.stateCollateral + const availabilityArgs = getLeverageV2RepayArgs(availabilityCollateral) + + if (+availabilityArgs.stateCollateral > 0) { + resp.isAvailable = await llamma.leverageV2.repayIsAvailable( + availabilityArgs.stateCollateral, + availabilityArgs.userCollateral, + availabilityArgs.userBorrowed, + address, + ) + } - if (resp.isAvailable && +collateral > 0) { - resp.isFullRepayment = await llamma.deleverage.isFullRepayment(collateral) - - const [{ stablecoins, routeIdx }, priceImpact] = await Promise.all([ - llamma.deleverage.repayStablecoins(collateral), - llamma.deleverage.priceImpact(collateral), - ]) - resp.receiveStablecoin = stablecoins - resp.routeName = await llamma.deleverage.getRouteName(routeIdx) - resp.priceImpact = priceImpact - resp.isHighImpact = +priceImpact > 0 && +maxSlippage > 0 ? +priceImpact > +maxSlippage : false - - if (!resp.isFullRepayment) { - const [healthFullResult, healthNotFullResult, bandsResult, pricesResult] = await Promise.allSettled([ - llamma.deleverage.repayHealth(collateral, true, address), - llamma.deleverage.repayHealth(collateral, false, address), - llamma.deleverage.repayBands(collateral, address), - llamma.deleverage.repayPrices(collateral, address), - ]) + if (resp.isAvailable && +repayArgs.stateCollateral > 0) { + const repayExpected = await llamma.leverageV2.repayExpectedBorrowed( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + slippage, + ) + resp.receiveStablecoin = repayExpected.totalBorrowed + resp.isFullRepayment = await llamma.leverageV2.repayIsFull( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + address, + ) + + resp.priceImpact = await llamma.leverageV2.repayPriceImpact( + repayArgs.stateCollateral, + repayArgs.userCollateral, + ) + resp.isHighImpact = isHigherThanMaxSlippage(resp.priceImpact, maxSlippage) + resp.routeName = ROUTE_AGGREGATOR_LABELS[RouteAggregator.Odos] + + if (!resp.isFullRepayment) { + const [healthFullResult, healthNotFullResult, bandsResult, pricesResult] = await Promise.allSettled([ + llamma.leverageV2.repayHealth( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + true, + address, + ), + llamma.leverageV2.repayHealth( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + false, + address, + ), + llamma.leverageV2.repayBands( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + address, + ), + llamma.leverageV2.repayPrices( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + address, + ), + ]) + + resp.healthFull = fulfilledValue(healthFullResult) ?? '' + resp.healthNotFull = fulfilledValue(healthNotFullResult) ?? '' + resp.bands = reverseBands(fulfilledValue(bandsResult) ?? [0, 0]) + resp.prices = fulfilledValue(pricesResult) ?? [] + } + } + } else { + const deleverageCollateral = +userState.collateral > 0 ? userState.collateral : collateral + if (+deleverageCollateral > 0) { + resp.isAvailable = await llamma.deleverage.isAvailable(deleverageCollateral) + } + + if (resp.isAvailable && +collateral > 0) { + resp.isFullRepayment = await llamma.deleverage.isFullRepayment(collateral) - resp.healthFull = fulfilledValue(healthFullResult) ?? '' - resp.healthNotFull = fulfilledValue(healthNotFullResult) ?? '' - resp.bands = reverseBands(fulfilledValue(bandsResult) ?? [0, 0]) - resp.prices = fulfilledValue(pricesResult) ?? [] + const [{ stablecoins, routeIdx }, priceImpact] = await Promise.all([ + llamma.deleverage.repayStablecoins(collateral), + llamma.deleverage.priceImpact(collateral), + ]) + resp.receiveStablecoin = stablecoins + resp.routeName = await llamma.deleverage.getRouteName(routeIdx) + resp.priceImpact = priceImpact + resp.isHighImpact = isHigherThanMaxSlippage(priceImpact, maxSlippage) + + if (!resp.isFullRepayment) { + const [healthFullResult, healthNotFullResult, bandsResult, pricesResult] = await Promise.allSettled([ + llamma.deleverage.repayHealth(collateral, true, address), + llamma.deleverage.repayHealth(collateral, false, address), + llamma.deleverage.repayBands(collateral, address), + llamma.deleverage.repayPrices(collateral, address), + ]) + + resp.healthFull = fulfilledValue(healthFullResult) ?? '' + resp.healthNotFull = fulfilledValue(healthNotFullResult) ?? '' + resp.bands = reverseBands(fulfilledValue(bandsResult) ?? [0, 0]) + resp.prices = fulfilledValue(pricesResult) ?? [] + } } } @@ -1031,9 +1125,26 @@ const loanDeleverage = { repay: async (activeKey: string, provider: Provider, llamma: Llamma, collateral: string, maxSlippage: string) => { log('deleverageRepay', llamma.collateralSymbol, collateral, maxSlippage) const resp = { activeKey, hash: '', error: '' } + const slippage = Number(maxSlippage) || 0 try { - resp.hash = await llamma.deleverage.repay(collateral, +maxSlippage) + if (hasV2Leverage(llamma)) { + const repayArgs = getLeverageV2RepayArgs(collateral) + await llamma.leverageV2.repayExpectedBorrowed( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + slippage, + ) + resp.hash = await llamma.leverageV2.repay( + repayArgs.stateCollateral, + repayArgs.userCollateral, + repayArgs.userBorrowed, + slippage, + ) + } else { + resp.hash = await llamma.deleverage.repay(collateral, slippage) + } await waitForTransaction(resp.hash, provider) return resp } catch (error) { diff --git a/apps/main/src/loan/utils/leverage.ts b/apps/main/src/loan/utils/leverage.ts new file mode 100644 index 0000000000..01620c7920 --- /dev/null +++ b/apps/main/src/loan/utils/leverage.ts @@ -0,0 +1,16 @@ +import { zeroAddress } from 'viem' +import type { Llamma } from '@/loan/types/loan.types' + +/** + * Mint market version with id < 6 is using v1 (leverageZap) + * Mint market with id >= 6 is using v2 leverage + */ +export type MintMarketVersion = 'v1' | 'v2' + +export const hasV1Leverage = (llamma: Llamma | null) => llamma?.leverageZap !== zeroAddress +export const hasV2Leverage = (llamma: Llamma | null) => !!llamma?.leverageV2.hasLeverage() +export const hasV1Deleverage = (llamma: Llamma | null) => llamma?.deleverageZap !== zeroAddress + +export const hasLeverage = (llamma: Llamma | null) => hasV1Leverage(llamma) || hasV2Leverage(llamma) +// hasV2Leverage works for deleverage as well +export const hasDeleverage = (llamma: Llamma | null) => hasV1Deleverage(llamma) || hasV2Leverage(llamma) diff --git a/apps/main/src/loan/utils/utilsLoan.ts b/apps/main/src/loan/utils/utilsLoan.ts index d1b48ba96a..591d97ca5f 100644 --- a/apps/main/src/loan/utils/utilsLoan.ts +++ b/apps/main/src/loan/utils/utilsLoan.ts @@ -14,3 +14,13 @@ export function getTokenName(llamma: Llamma | null | undefined) { const [stablecoin, collateral] = llamma?.coins ?? ['', ''] return { stablecoin, collateral } } + +export const getLeverageV2RepayArgs = (stateCollateral: string) => ({ + stateCollateral: stateCollateral, + // amount for repay/deleverage only from collateral for now + userCollateral: '0', + userBorrowed: '0', +}) + +export const isHigherThanMaxSlippage = (priceImpact: string, maxSlippage: string) => + +priceImpact > 0 && +maxSlippage > 0 ? +priceImpact > +maxSlippage : false