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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/components/transactions/Repay/RepayError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Trans } from '@lingui/macro';
import { useModalContext } from 'src/hooks/useModal';

export enum RepayErrorType {
ZERO_LTV_REPAY_BLOCKED,
}

interface RepayErrorProps {
assetsBlockingWithdraw: string[];
repayWithATokens: boolean;
}

export const useRepayError = ({ assetsBlockingWithdraw, repayWithATokens }: RepayErrorProps) => {
const { mainTxState: repayTxState } = useModalContext();

let blockingError: RepayErrorType | undefined = undefined;

if (!repayTxState.success && !repayTxState.txHash) {
if (repayWithATokens && assetsBlockingWithdraw.length > 0) {
blockingError = RepayErrorType.ZERO_LTV_REPAY_BLOCKED;
}
}

const errors = {
[RepayErrorType.ZERO_LTV_REPAY_BLOCKED]: (
<Trans>
Assets with zero LTV ({assetsBlockingWithdraw.join(', ')}) must be withdrawn or disabled as
collateral to repay with aTokens. Please repay using your wallet balance instead.
</Trans>
),
};

return {
blockingError,
errorComponent: blockingError !== undefined ? errors[blockingError] : null,
};
};
31 changes: 25 additions & 6 deletions src/components/transactions/Repay/RepayModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { InterestRate } from '@aave/contract-helpers';
import { Trans } from '@lingui/macro';
import Typography from '@mui/material/Typography';
import React, { useState } from 'react';
import { Warning } from 'src/components/primitives/Warning';
import { UserAuthenticated } from 'src/components/UserAuthenticated';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
import { ModalContextType, ModalType, useModalContext } from 'src/hooks/useModal';
import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw';
import { useRootStore } from 'src/store/root';
import { isFeatureEnabled } from 'src/utils/marketsAndNetworksConfig';

Expand All @@ -23,10 +26,9 @@ export const RepayModal = () => {
const currentMarketData = useRootStore((store) => store.currentMarketData);
const [repayType, setRepayType] = useState(RepayType.BALANCE);

// repay with collateral is only possible:
// 1. on chains with paraswap deployed
// 2. when you have a different supplied(not necessarily collateral) asset then the one your debt is in
// For repaying your debt with the same assets aToken you can use repayWithAToken on aave protocol v3
const assetsBlockingWithdraw = useZeroLTVBlockingWithdraw();
const hasZeroLTVBlocking = assetsBlockingWithdraw.length > 0;

const collateralRepayPossible =
isFeatureEnabled.collateralRepay(currentMarketData) &&
userReserves.some(
Expand All @@ -35,6 +37,8 @@ export const RepayModal = () => {
userReserve.underlyingAsset !== args.underlyingAsset
);

const collateralRepayBlocked = collateralRepayPossible && hasZeroLTVBlocking;

const handleClose = () => {
setRepayType(RepayType.BALANCE);
close();
Expand All @@ -49,10 +53,25 @@ export const RepayModal = () => {
{(user) => (
<>
{collateralRepayPossible && !mainTxState.txHash && (
<RepayTypeSelector repayType={repayType} setRepayType={setRepayType} />
<RepayTypeSelector
repayType={repayType}
setRepayType={setRepayType}
collateralDisabled={collateralRepayBlocked}
/>
)}
{collateralRepayBlocked && (
<Warning severity="warning" sx={{ mt: 2, mb: 2 }}>
<Typography variant="caption">
<Trans>
Repay with collateral is unavailable because you have assets with zero LTV
({assetsBlockingWithdraw.join(', ')}) enabled as collateral. Withdraw or
disable them as collateral first.
</Trans>
</Typography>
</Warning>
)}
{repayType === RepayType.BALANCE && <RepayModalContent {...params} user={user} />}
{repayType === RepayType.COLLATERAL && (
{repayType === RepayType.COLLATERAL && !collateralRepayBlocked && (
<RepayWithCollateralModalContent
{...params}
debtType={args.currentRateMode}
Expand Down
42 changes: 21 additions & 21 deletions src/components/transactions/Repay/RepayModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useAppDataContext,
} from 'src/hooks/app-data-provider/useAppDataProvider';
import { useModalContext } from 'src/hooks/useModal';
import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw';
import { useRootStore } from 'src/store/root';
import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities';
import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig';
Expand All @@ -30,6 +31,7 @@ import {
TxModalDetails,
} from '../FlowCommons/TxModalDetails';
import { RepayActions } from './RepayActions';
import { useRepayError } from './RepayError';

interface RepayAsset extends Asset {
balance: string;
Expand Down Expand Up @@ -57,7 +59,8 @@ export const RepayModalContent = ({
])
);

// states
const assetsBlockingWithdraw = useZeroLTVBlockingWithdraw();

const [tokenToRepayWith, setTokenToRepayWith] = useState<RepayAsset>({
address: poolReserve.underlyingAsset,
symbol: poolReserve.symbol,
Expand All @@ -76,6 +79,13 @@ export const RepayModalContent = ({

const repayWithATokens = tokenToRepayWith.address === poolReserve.aTokenAddress;

const isZeroLTVBlocked = repayWithATokens && assetsBlockingWithdraw.length > 0;

const { blockingError, errorComponent } = useRepayError({
assetsBlockingWithdraw,
repayWithATokens,
});

const debt = userReserve?.variableBorrows || '0';
const debtUSD = new BigNumber(debt)
.multipliedBy(poolReserve.formattedPriceInMarketReferenceCurrency)
Expand All @@ -86,7 +96,6 @@ export const RepayModalContent = ({
.multipliedBy('1.0025')
.decimalPlaces(poolReserve.decimals, BigNumber.ROUND_UP);

// calculate max amount abailable to repay
let maxAmountToRepay: BigNumber;
let balance: string;
if (repayWithATokens) {
Expand Down Expand Up @@ -116,15 +125,8 @@ export const RepayModalContent = ({
synthetixProxyByChainId[currentChainId].toLowerCase() ===
reserve.underlyingAsset.toLowerCase())
) {
// for native token and synthetix (only mainnet) we can't send -1 as
// contract does not accept max unit256
setRepayMax(safeAmountToRepayAll.toString(10));
} else {
// -1 can always be used for v3 otherwise
// for v2 we can onl use -1 when user has more balance than max debt to repay
// this is accounted for when maxAmountToRepay.eq(debt) as maxAmountToRepay is
// min between debt and walletbalance, so if it enters here for v2 it means
// balance is bigger and will be able to transact with -1
setRepayMax('-1');
}
} else {
Expand All @@ -136,11 +138,8 @@ export const RepayModalContent = ({
}
};

// token info
useEffect(() => {
const repayTokens: RepayAsset[] = [];
// set possible repay tokens
// if wrapped reserve push both wrapped / native
if (poolReserve.symbol === networkConfig.wrappedBaseAssetSymbol) {
const nativeTokenWalletBalance = valueToBigNumber(nativeBalance);
const maxNativeToken = BigNumber.max(
Expand All @@ -153,7 +152,6 @@ export const RepayModalContent = ({
balance: maxNativeToken.toString(10),
});
}
// push reserve asset
const minReserveTokenRepay = BigNumber.min(valueToBigNumber(tokenBalance), debt);
const maxReserveTokenForRepay = BigNumber.max(minReserveTokenRepay, tokenBalance);
repayTokens.push({
Expand All @@ -162,7 +160,6 @@ export const RepayModalContent = ({
iconSymbol: poolReserve.iconSymbol,
balance: maxReserveTokenForRepay.toString(10),
});
// push reserve aToken
if (
currentMarketData.v3 &&
!displayGhoForMintableMarket({ symbol: poolReserve.symbol, currentMarket })
Expand All @@ -184,7 +181,6 @@ export const RepayModalContent = ({
setTokenToRepayWith(repayTokens[0]);
}, []);

// debt remaining after repay
const amountAfterRepay = valueToBigNumber(debt)
.minus(amount || '0')
.toString(10);
Expand All @@ -195,8 +191,6 @@ export const RepayModalContent = ({

const maxRepayWithDustRemaining = isMaxSelected && amountAfterRepayInUsd.toNumber() > 0;

// health factor calculations
// we use usd values instead of MarketreferenceCurrency so it has same precision
let newHF = user?.healthFactor;
if (amount) {
let collateralBalanceMarketReferenceCurrency: BigNumberValue = user?.totalCollateralUSD || '0';
Expand All @@ -223,7 +217,6 @@ export const RepayModalContent = ({
: calculatedHealthFactor.toString(10);
}

// calculating input usd value
const usdValue = valueToBigNumber(amount).multipliedBy(reserve.priceInUSD);

if (repayTxState.success)
Expand All @@ -249,12 +242,18 @@ export const RepayModalContent = ({
balanceText={<Trans>Wallet balance</Trans>}
/>

{blockingError !== undefined && (
<Typography variant="helperText" color="error.main">
{errorComponent}
</Typography>
)}

{maxRepayWithDustRemaining && (
<Typography color="warning.main" variant="helperText">
<Trans>
You don’t have enough funds in your wallet to repay the full amount. If you proceed to
repay with your current amount of funds, you will still have a small borrowing position
in your dashboard.
You don\u2019t have enough funds in your wallet to repay the full amount. If you proceed
to repay with your current amount of funds, you will still have a small borrowing
position in your dashboard.
</Trans>
</Typography>
)}
Expand Down Expand Up @@ -305,6 +304,7 @@ export const RepayModalContent = ({
setShowUSDTResetWarning={setShowUSDTResetWarning}
chainId={currentChainId}
maxAmountToRepay={maxAmountToRepay.toString(10)}
blocked={isZeroLTVBlocked}
/>
</>
);
Expand Down
10 changes: 8 additions & 2 deletions src/components/transactions/Repay/RepayTypeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export enum RepayType {
export function RepayTypeSelector({
repayType,
setRepayType,
collateralDisabled,
}: {
repayType: RepayType;
setRepayType: (type: RepayType) => void;
collateralDisabled?: boolean;
}) {
const [trackEvent, currentMarketData] = useRootStore(
useShallow((store) => [store.trackEvent, store.currentMarketData])
Expand All @@ -32,7 +34,11 @@ export function RepayTypeSelector({
color="primary"
value={repayType}
exclusive
onChange={(_, value) => setRepayType(value)}
onChange={(_, value) => {
if (value !== null) {
setRepayType(value);
}
}}
>
<StyledTxModalToggleButton
value={RepayType.BALANCE}
Expand All @@ -46,7 +52,7 @@ export function RepayTypeSelector({

<StyledTxModalToggleButton
value={RepayType.COLLATERAL}
disabled={repayType === RepayType.COLLATERAL}
disabled={repayType === RepayType.COLLATERAL || collateralDisabled}
onClick={() => trackEvent(REPAY_MODAL.SWITCH_REPAY_TYPE, { repayType: 'Collateral' })}
>
<Typography variant="buttonM">
Expand Down
2 changes: 1 addition & 1 deletion src/locales/el/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/locales/en/messages.js

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,10 @@ msgstr "We couldn't detect a wallet. Connect a wallet to stake and view your bal
msgid "Destination"
msgstr "Destination"

#: src/components/transactions/Repay/RepayModal.tsx
msgid "Repay with collateral is unavailable because you have assets with zero LTV ({0}) enabled as collateral. Withdraw or disable them as collateral first."
msgstr "Repay with collateral is unavailable because you have assets with zero LTV ({0}) enabled as collateral. Withdraw or disable them as collateral first."

#: src/modules/sGho/SGhoDepositPanel.tsx
#: src/modules/sGho/SGhoDepositPanel.tsx
#: src/modules/staking/GhoStakingPanel.tsx
Expand Down Expand Up @@ -2053,6 +2057,10 @@ msgstr "Price impact is the spread between the total value of the entry tokens s
msgid "Send cancel"
msgstr "Send cancel"

#: src/components/transactions/Repay/RepayModalContent.tsx
msgid "You don\\u2019t have enough funds in your wallet to repay the full amount. If you proceed to repay with your current amount of funds, you will still have a small borrowing position in your dashboard."
msgstr "You don\\u2019t have enough funds in your wallet to repay the full amount. If you proceed to repay with your current amount of funds, you will still have a small borrowing position in your dashboard."

#: src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListItem.tsx
#: src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsListMobileItem.tsx
#: src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsListItem.tsx
Expand Down Expand Up @@ -2128,6 +2136,10 @@ msgstr "YAE"
msgid "Claiming all merit rewards only"
msgstr "Claiming all merit rewards only"

#: src/components/transactions/Repay/RepayError.tsx
msgid "Assets with zero LTV ({0}) must be withdrawn or disabled as collateral to repay with aTokens. Please repay using your wallet balance instead."
msgstr "Assets with zero LTV ({0}) must be withdrawn or disabled as collateral to repay with aTokens. Please repay using your wallet balance instead."

#: src/components/transactions/FlowCommons/BaseCancelled.tsx
#: src/components/transactions/FlowCommons/BaseSuccess.tsx
#: src/components/transactions/FlowCommons/BaseWaiting.tsx
Expand Down Expand Up @@ -2610,10 +2622,6 @@ msgstr "Ltv validation failed"
msgid "Maximum amount available to supply is <0/> {0} (<1/>)."
msgstr "Maximum amount available to supply is <0/> {0} (<1/>)."

#: src/components/transactions/Repay/RepayModalContent.tsx
msgid "You don’t have enough funds in your wallet to repay the full amount. If you proceed to repay with your current amount of funds, you will still have a small borrowing position in your dashboard."
msgstr "You don’t have enough funds in your wallet to repay the full amount. If you proceed to repay with your current amount of funds, you will still have a small borrowing position in your dashboard."

#: src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
#: src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
msgid "Assets to borrow"
Expand Down
2 changes: 1 addition & 1 deletion src/locales/es/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/locales/fr/messages.js

Large diffs are not rendered by default.