Skip to content

Commit 85a5271

Browse files
authored
fix: restore collateral change modal (#2769)
1 parent 05cc75f commit 85a5271

File tree

10 files changed

+322
-5
lines changed

10 files changed

+322
-5
lines changed

pages/_app.page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ const CancelCowOrderModal = dynamic(() =>
8989
const ReadOnlyModal = dynamic(() =>
9090
import('src/components/WalletConnection/ReadOnlyModal').then((module) => module.ReadOnlyModal)
9191
);
92+
const CollateralChangeModal = dynamic(() =>
93+
import('src/components/transactions/CollateralChange/CollateralChangeModal').then(
94+
(module) => module.CollateralChangeModal
95+
)
96+
);
9297

9398
// Client-side cache, shared for the whole session of the user in the browser.
9499
const clientSideEmotionCache = createEmotionCache();
@@ -166,6 +171,7 @@ export default function MyApp(props: MyAppProps) {
166171
<WithdrawModal />
167172
<BorrowModal />
168173
<RepayModal />
174+
<CollateralChangeModal />
169175
<ClaimRewardsModal />
170176
<EmodeModal />
171177
<FaucetModal />
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ProtocolAction } from '@aave/contract-helpers';
2+
import { Trans } from '@lingui/macro';
3+
import { useTransactionHandler } from 'src/helpers/useTransactionHandler';
4+
import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
5+
import { useRootStore } from 'src/store/root';
6+
7+
import { TxActionsWrapper } from '../TxActionsWrapper';
8+
9+
export type CollateralChangeActionsProps = {
10+
poolReserve: ComputedReserveData;
11+
isWrongNetwork: boolean;
12+
usageAsCollateral: boolean;
13+
blocked: boolean;
14+
symbol: string;
15+
};
16+
17+
export const CollateralChangeActions = ({
18+
poolReserve,
19+
isWrongNetwork,
20+
usageAsCollateral,
21+
blocked,
22+
symbol,
23+
}: CollateralChangeActionsProps) => {
24+
const setUsageAsCollateral = useRootStore((state) => state.setUsageAsCollateral);
25+
26+
const { action, loadingTxns, mainTxState, requiresApproval } = useTransactionHandler({
27+
tryPermit: false,
28+
protocolAction: ProtocolAction.setUsageAsCollateral,
29+
eventTxInfo: {
30+
assetName: poolReserve.name,
31+
asset: poolReserve.underlyingAsset,
32+
previousState: (!usageAsCollateral).toString(),
33+
newState: usageAsCollateral.toString(),
34+
},
35+
36+
handleGetTxns: async () => {
37+
return setUsageAsCollateral({
38+
reserve: poolReserve.underlyingAsset,
39+
usageAsCollateral,
40+
});
41+
},
42+
skip: blocked,
43+
});
44+
45+
return (
46+
<TxActionsWrapper
47+
requiresApproval={requiresApproval}
48+
blocked={blocked}
49+
preparingTransactions={loadingTxns}
50+
mainTxState={mainTxState}
51+
isWrongNetwork={isWrongNetwork}
52+
actionText={
53+
usageAsCollateral ? (
54+
<Trans>Enable {symbol} as collateral</Trans>
55+
) : (
56+
<Trans>Disable {symbol} as collateral</Trans>
57+
)
58+
}
59+
actionInProgressText={<Trans>Pending...</Trans>}
60+
handleAction={action}
61+
/>
62+
);
63+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Trans } from '@lingui/macro';
2+
import React from 'react';
3+
import { UserAuthenticated } from 'src/components/UserAuthenticated';
4+
import { ModalContextType, ModalType, useModalContext } from 'src/hooks/useModal';
5+
6+
import { BasicModal } from '../../primitives/BasicModal';
7+
import { ModalWrapper } from '../FlowCommons/ModalWrapper';
8+
import { CollateralChangeModalContent } from './CollateralChangeModalContent';
9+
10+
export const CollateralChangeModal = () => {
11+
const { type, close, args } = useModalContext() as ModalContextType<{
12+
underlyingAsset: string;
13+
}>;
14+
return (
15+
<BasicModal open={type === ModalType.CollateralChange} setOpen={close}>
16+
<ModalWrapper title={<Trans>Review tx</Trans>} underlyingAsset={args.underlyingAsset}>
17+
{(params) => (
18+
<UserAuthenticated>
19+
{(user) => <CollateralChangeModalContent {...params} user={user} />}
20+
</UserAuthenticated>
21+
)}
22+
</ModalWrapper>
23+
</BasicModal>
24+
);
25+
};
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { calculateHealthFactorFromBalancesBigUnits, valueToBigNumber } from '@aave/math-utils';
2+
import { Trans } from '@lingui/macro';
3+
import { Typography } from '@mui/material';
4+
import { useEffect, useState } from 'react';
5+
import { Warning } from 'src/components/primitives/Warning';
6+
import { ExtendedFormattedUser } from 'src/hooks/app-data-provider/useAppDataProvider';
7+
import { useAssetCaps } from 'src/hooks/useAssetCaps';
8+
import { useModalContext } from 'src/hooks/useModal';
9+
import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw';
10+
11+
import { GasEstimationError } from '../FlowCommons/GasEstimationError';
12+
import { ModalWrapperProps } from '../FlowCommons/ModalWrapper';
13+
import { TxSuccessView } from '../FlowCommons/Success';
14+
import { DetailsHFLine, DetailsNumberLine, TxModalDetails } from '../FlowCommons/TxModalDetails';
15+
import { IsolationModeWarning } from '../Warnings/IsolationModeWarning';
16+
import { CollateralChangeActions } from './CollateralChangeActions';
17+
18+
export type CollateralChangeModalContentProps = {
19+
underlyingAsset: string;
20+
};
21+
22+
export enum ErrorType {
23+
DO_NOT_HAVE_SUPPLIES_IN_THIS_CURRENCY,
24+
CAN_NOT_USE_THIS_CURRENCY_AS_COLLATERAL,
25+
CAN_NOT_SWITCH_USAGE_AS_COLLATERAL_MODE,
26+
ZERO_LTV_WITHDRAW_BLOCKED,
27+
}
28+
29+
export const CollateralChangeModalContent = ({
30+
poolReserve,
31+
userReserve,
32+
isWrongNetwork,
33+
symbol,
34+
user,
35+
}: ModalWrapperProps & { user: ExtendedFormattedUser }) => {
36+
const { gasLimit, mainTxState: collateralChangeTxState, txError } = useModalContext();
37+
const { debtCeiling } = useAssetCaps();
38+
39+
const [collateralEnabled, setCollateralEnabled] = useState(
40+
userReserve.usageAsCollateralEnabledOnUser
41+
);
42+
43+
// Health factor calculations
44+
const usageAsCollateralModeAfterSwitch = !userReserve.usageAsCollateralEnabledOnUser;
45+
const currenttotalCollateralMarketReferenceCurrency = valueToBigNumber(
46+
user.totalCollateralMarketReferenceCurrency
47+
);
48+
49+
// Messages
50+
const showEnableIsolationModeMsg = !poolReserve.isIsolated && usageAsCollateralModeAfterSwitch;
51+
const showDisableIsolationModeMsg = !poolReserve.isIsolated && !usageAsCollateralModeAfterSwitch;
52+
const showEnterIsolationModeMsg = poolReserve.isIsolated && usageAsCollateralModeAfterSwitch;
53+
const showExitIsolationModeMsg = poolReserve.isIsolated && !usageAsCollateralModeAfterSwitch;
54+
55+
const totalCollateralAfterSwitchETH = currenttotalCollateralMarketReferenceCurrency[
56+
usageAsCollateralModeAfterSwitch ? 'plus' : 'minus'
57+
](userReserve.underlyingBalanceMarketReferenceCurrency);
58+
59+
const healthFactorAfterSwitch = calculateHealthFactorFromBalancesBigUnits({
60+
collateralBalanceMarketReferenceCurrency: totalCollateralAfterSwitchETH,
61+
borrowBalanceMarketReferenceCurrency: user.totalBorrowsMarketReferenceCurrency,
62+
currentLiquidationThreshold: user.currentLiquidationThreshold,
63+
});
64+
65+
const assetsBlockingWithdraw = useZeroLTVBlockingWithdraw();
66+
67+
// error handling
68+
let blockingError: ErrorType | undefined = undefined;
69+
if (assetsBlockingWithdraw.length > 0 && !assetsBlockingWithdraw.includes(poolReserve.symbol)) {
70+
blockingError = ErrorType.ZERO_LTV_WITHDRAW_BLOCKED;
71+
} else if (valueToBigNumber(userReserve.underlyingBalance).eq(0)) {
72+
blockingError = ErrorType.DO_NOT_HAVE_SUPPLIES_IN_THIS_CURRENCY;
73+
} else if (
74+
(!userReserve.usageAsCollateralEnabledOnUser &&
75+
poolReserve.reserveLiquidationThreshold === '0') ||
76+
poolReserve.reserveLiquidationThreshold === '0'
77+
) {
78+
blockingError = ErrorType.CAN_NOT_USE_THIS_CURRENCY_AS_COLLATERAL;
79+
} else if (
80+
userReserve.usageAsCollateralEnabledOnUser &&
81+
user.totalBorrowsMarketReferenceCurrency !== '0' &&
82+
healthFactorAfterSwitch.lte('1')
83+
) {
84+
blockingError = ErrorType.CAN_NOT_SWITCH_USAGE_AS_COLLATERAL_MODE;
85+
}
86+
87+
// error render handling
88+
const BlockingError: React.FC = () => {
89+
switch (blockingError) {
90+
case ErrorType.DO_NOT_HAVE_SUPPLIES_IN_THIS_CURRENCY:
91+
return <Trans>You do not have supplies in this currency</Trans>;
92+
case ErrorType.CAN_NOT_USE_THIS_CURRENCY_AS_COLLATERAL:
93+
return <Trans>You can not use this currency as collateral</Trans>;
94+
case ErrorType.CAN_NOT_SWITCH_USAGE_AS_COLLATERAL_MODE:
95+
return (
96+
<Trans>
97+
You can not switch usage as collateral mode for this currency, because it will cause
98+
collateral call
99+
</Trans>
100+
);
101+
case ErrorType.ZERO_LTV_WITHDRAW_BLOCKED:
102+
return (
103+
<Trans>
104+
Assets with zero LTV ({assetsBlockingWithdraw.join(', ')}) must be withdrawn or disabled
105+
as collateral to perform this action
106+
</Trans>
107+
);
108+
default:
109+
return null;
110+
}
111+
};
112+
113+
// Effect to handle changes in collateral mode after switch as polling is fetching reserve state different after successful tx
114+
useEffect(() => {
115+
if (collateralChangeTxState.success) {
116+
setCollateralEnabled(usageAsCollateralModeAfterSwitch);
117+
}
118+
}, [collateralChangeTxState.success, collateralEnabled]);
119+
120+
if (collateralChangeTxState.success)
121+
return <TxSuccessView collateral={collateralEnabled} symbol={poolReserve.symbol} />;
122+
123+
return (
124+
<>
125+
{showEnableIsolationModeMsg && (
126+
<Warning severity="warning" icon={false} sx={{ mb: 3 }}>
127+
<Trans>
128+
Enabling this asset as collateral increases your borrowing power and Health Factor.
129+
However, it can get liquidated if your health factor drops below 1.
130+
</Trans>
131+
</Warning>
132+
)}
133+
134+
{showDisableIsolationModeMsg && (
135+
<Warning severity="warning" icon={false} sx={{ mb: 3 }}>
136+
<Trans>
137+
Disabling this asset as collateral affects your borrowing power and Health Factor.
138+
</Trans>
139+
</Warning>
140+
)}
141+
142+
{showEnterIsolationModeMsg && <IsolationModeWarning asset={poolReserve.symbol} />}
143+
144+
{showExitIsolationModeMsg && (
145+
<Warning severity="info" icon={false} sx={{ mb: 3 }}>
146+
<Trans>You will exit isolation mode and other tokens can now be used as collateral</Trans>
147+
</Warning>
148+
)}
149+
150+
{poolReserve.isIsolated && debtCeiling.determineWarningDisplay({ debtCeiling })}
151+
152+
<TxModalDetails gasLimit={gasLimit}>
153+
<DetailsNumberLine
154+
symbol={poolReserve.symbol}
155+
iconSymbol={poolReserve.iconSymbol}
156+
description={<Trans>Supply balance</Trans>}
157+
value={userReserve.underlyingBalance}
158+
/>
159+
<DetailsHFLine
160+
visibleHfChange={true}
161+
healthFactor={user.healthFactor}
162+
futureHealthFactor={healthFactorAfterSwitch.toString(10)}
163+
/>
164+
</TxModalDetails>
165+
166+
{blockingError !== undefined && (
167+
<Typography variant="helperText" color="error.main">
168+
<BlockingError />
169+
</Typography>
170+
)}
171+
172+
{txError && <GasEstimationError txError={txError} />}
173+
174+
<CollateralChangeActions
175+
symbol={symbol}
176+
poolReserve={poolReserve}
177+
usageAsCollateral={usageAsCollateralModeAfterSwitch}
178+
isWrongNetwork={isWrongNetwork}
179+
blocked={blockingError !== undefined}
180+
/>
181+
</>
182+
);
183+
};

src/hooks/useModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum ModalType {
1414
Withdraw,
1515
Borrow,
1616
Repay,
17+
CollateralChange,
1718
Stake,
1819
Unstake,
1920
StakeCooldown,
@@ -264,7 +265,7 @@ export const ModalContextProvider: React.FC<PropsWithChildren> = ({ children })
264265
funnel,
265266
usageAsCollateralEnabledOnUser
266267
) => {
267-
setType(ModalType.CollateralSwap);
268+
setType(ModalType.CollateralChange);
268269
setArgs({ underlyingAsset });
269270
trackEvent(GENERAL.OPEN_MODAL, {
270271
modal: 'Toggle Collateral',

src/locales/el/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/locales/en/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)