diff --git a/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts b/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts index 7edac00438..9a87334ece 100644 --- a/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts +++ b/cypress/e2e/1-v3-markets/3-polygon-v3-market/e-mode.polygon-v3.cy.ts @@ -45,7 +45,6 @@ const testData = { assets.polygonV3Market.EURS, assets.polygonV3Market.jEUR, assets.polygonV3Market.agEUR, - assets.polygonV3Market.miMATIC, ], }, }; diff --git a/package.json b/package.json index 168e999951..28d2f7c31b 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "test:ci": "jest --ci" }, "dependencies": { - "@aave/contract-helpers": "1.9.0", - "@aave/math-utils": "1.9.0", + "@aave/contract-helpers": "1.9.1-cae134738b8899ec4f5227d4932cb8f8ab5e4acd.0+6c081d5", + "@aave/math-utils": "1.9.1-cae134738b8899ec4f5227d4932cb8f8ab5e4acd.0+6c081d5", "@emotion/cache": "11.10.3", "@emotion/react": "11.10.4", "@emotion/server": "latest", diff --git a/src/components/TextWithTooltip.tsx b/src/components/TextWithTooltip.tsx index 49829d37e6..4704457804 100644 --- a/src/components/TextWithTooltip.tsx +++ b/src/components/TextWithTooltip.tsx @@ -9,6 +9,7 @@ export interface TextWithTooltipProps extends TypographyProps { text?: ReactNode; icon?: ReactNode; iconSize?: number; + iconMargin?: number; color?: string; // eslint-disable-next-line children?: ReactElement>; @@ -18,6 +19,7 @@ export const TextWithTooltip = ({ text, icon, iconSize = 14, + iconMargin, color, children, ...rest @@ -39,7 +41,7 @@ export const TextWithTooltip = ({ borderRadius: '50%', p: 0, minWidth: 0, - ml: 0.5, + ml: iconMargin || 0.5, }} > { - return ( - Approval}> - - - Before supplying, you need to approve its usage by the Aave protocol. You can learn more - in our{' '} - - FAQ - - - - - ); -}; diff --git a/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx b/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx deleted file mode 100644 index 900103a0a4..0000000000 --- a/src/components/infoModalContents/RetryWithApprovalInfoContent.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Trans } from '@lingui/macro'; -import Typography from '@mui/material/Typography'; - -import { InfoContentWrapper } from './InfoContentWrapper'; - -export const RetryWithApprovalInfoContent = () => { - return ( - Retry with Approval}> - - Define Retry with Approval text - - - ); -}; diff --git a/src/components/infoTooltips/ApprovalTooltip.tsx b/src/components/infoTooltips/ApprovalTooltip.tsx new file mode 100644 index 0000000000..11715b2013 --- /dev/null +++ b/src/components/infoTooltips/ApprovalTooltip.tsx @@ -0,0 +1,19 @@ +import { Trans } from '@lingui/macro'; + +import { Link } from '../primitives/Link'; +import { TextWithTooltip, TextWithTooltipProps } from '../TextWithTooltip'; + +export const ApprovalTooltip = ({ ...rest }: TextWithTooltipProps) => { + return ( + + + To continue, you need to grant Aave smart contracts permission to move your funds from your + wallet. Depending on the asset and wallet you use, it is done by signing the permission + message (gas free), or by submitting an approval transaction (requires gas).{' '} + + Learn more + + + + ); +}; diff --git a/src/components/transactions/Borrow/BorrowActions.tsx b/src/components/transactions/Borrow/BorrowActions.tsx index 905a8ceae8..ca4219a76c 100644 --- a/src/components/transactions/Borrow/BorrowActions.tsx +++ b/src/components/transactions/Borrow/BorrowActions.tsx @@ -57,7 +57,7 @@ export const BorrowActions = ({ handleAction={action} actionText={Borrow {symbol}} actionInProgressText={Borrowing {symbol}} - handleApproval={() => approval(amountToBorrow, poolAddress)} + handleApproval={() => approval({ amount: amountToBorrow, underlyingAsset: poolAddress })} requiresApproval={requiresApproval} preparingTransactions={loadingTxns} sx={sx} diff --git a/src/components/transactions/FlowCommons/ApprovalMethodToggleButton.tsx b/src/components/transactions/FlowCommons/ApprovalMethodToggleButton.tsx new file mode 100644 index 0000000000..d05c0aa531 --- /dev/null +++ b/src/components/transactions/FlowCommons/ApprovalMethodToggleButton.tsx @@ -0,0 +1,93 @@ +import { CheckIcon } from '@heroicons/react/outline'; +import { CogIcon } from '@heroicons/react/solid'; +import { Trans } from '@lingui/macro'; +import { + Box, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + SvgIcon, + Typography, +} from '@mui/material'; +import * as React from 'react'; +import { ApprovalMethod } from 'src/store/walletSlice'; + +interface ApprovalMethodToggleButtonProps { + currentMethod: ApprovalMethod; + setMethod: (newMethod: ApprovalMethod) => void; +} + +export const ApprovalMethodToggleButton = ({ + currentMethod, + setMethod, +}: ApprovalMethodToggleButtonProps) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + {currentMethod} + + + + + + + + { + if (currentMethod === ApprovalMethod.APPROVE) { + setMethod(ApprovalMethod.PERMIT); + } + handleClose(); + }} + > + + {ApprovalMethod.PERMIT} + + + {currentMethod === ApprovalMethod.PERMIT && } + + + + { + if (currentMethod === ApprovalMethod.PERMIT) { + setMethod(ApprovalMethod.APPROVE); + } + handleClose(); + }} + > + + {ApprovalMethod.APPROVE} + + + {currentMethod === ApprovalMethod.APPROVE && } + + + + + ); +}; diff --git a/src/components/transactions/FlowCommons/LeftHelperText.tsx b/src/components/transactions/FlowCommons/LeftHelperText.tsx deleted file mode 100644 index 1d34488358..0000000000 --- a/src/components/transactions/FlowCommons/LeftHelperText.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { CheckIcon } from '@heroicons/react/outline'; -import { Trans } from '@lingui/macro'; -import { Box, Typography, useTheme } from '@mui/material'; - -import { ApprovalInfoContent } from '../../infoModalContents/ApprovalInfoContent'; -import { RetryWithApprovalInfoContent } from '../../infoModalContents/RetryWithApprovalInfoContent'; -import { TextWithModal } from '../../TextWithModal'; - -export type LeftHelperTextProps = { - error?: string; - approvalHash?: string; - amount?: string; -}; - -export const LeftHelperText = ({ error, approvalHash, amount }: LeftHelperTextProps) => { - const theme = useTheme(); - - return ( - - {approvalHash && ( - <> - - - Approve confirmed - - - )} - - {error && ( - Retry What?} - iconSize={13} - iconColor={theme.palette.text.secondary} - withContentButton - variant="helperText" - color="text.secondary" - > - - - )} - - {!approvalHash && !error && amount && ( - Why do I need to approve?} - iconSize={13} - iconColor={theme.palette.text.secondary} - withContentButton - variant="helperText" - color="text.secondary" - > - - - )} - - ); -}; diff --git a/src/components/transactions/FlowCommons/RightHelperText.tsx b/src/components/transactions/FlowCommons/RightHelperText.tsx index 1fc34fcaa9..d950dff498 100644 --- a/src/components/transactions/FlowCommons/RightHelperText.tsx +++ b/src/components/transactions/FlowCommons/RightHelperText.tsx @@ -1,11 +1,15 @@ import { ExternalLinkIcon } from '@heroicons/react/outline'; import { Trans } from '@lingui/macro'; -import { Box, Link, SvgIcon } from '@mui/material'; +import { Box, Link, SvgIcon, Typography } from '@mui/material'; +import { ApprovalMethodToggleButton } from 'src/components/transactions/FlowCommons/ApprovalMethodToggleButton'; import { MOCK_SIGNED_HASH } from 'src/helpers/useTransactionHandler'; import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; +import { useRootStore } from 'src/store/root'; +import { ApprovalMethod } from 'src/store/walletSlice'; export type RightHelperTextProps = { approvalHash?: string; + tryPermit?: boolean; }; const ExtLinkIcon = () => ( @@ -14,32 +18,48 @@ const ExtLinkIcon = () => ( ); -export const RightHelperText = ({ approvalHash }: RightHelperTextProps) => { +export const RightHelperText = ({ approvalHash, tryPermit }: RightHelperTextProps) => { + const { walletApprovalMethodPreference, setWalletApprovalMethodPreference } = useRootStore(); + const usingPermit = tryPermit && walletApprovalMethodPreference; const { currentNetworkConfig } = useProtocolDataContext(); const isSigned = approvalHash === MOCK_SIGNED_HASH; - // a signature will not be reviewable on etherscan - if (!approvalHash || isSigned) return null; - return ( - - {approvalHash && ( - - Review approval tx details - - - )} - - ); + // a signature is not submitted on-chain so there is no link to review + if (!approvalHash && !isSigned && tryPermit) + return ( + + + Approve with  + + setWalletApprovalMethodPreference(method)} + /> + + ); + if (approvalHash && !usingPermit) + return ( + + {approvalHash && ( + + Review approval tx details + + + )} + + ); + return <>; }; diff --git a/src/components/transactions/Repay/CollateralRepayActions.tsx b/src/components/transactions/Repay/CollateralRepayActions.tsx index f7fdd6e181..3327f9c40e 100644 --- a/src/components/transactions/Repay/CollateralRepayActions.tsx +++ b/src/components/transactions/Repay/CollateralRepayActions.tsx @@ -85,7 +85,9 @@ export const CollateralRepayActions = ({ sx={sx} {...props} handleAction={action} - handleApproval={() => approval()} + handleApproval={() => + approval({ amount: repayWithAmount, underlyingAsset: poolReserve.aTokenAddress }) + } actionText={Repay {symbol}} actionInProgressText={Repaying {symbol}} /> diff --git a/src/components/transactions/Repay/RepayActions.tsx b/src/components/transactions/Repay/RepayActions.tsx index f2987920b3..45a2372868 100644 --- a/src/components/transactions/Repay/RepayActions.tsx +++ b/src/components/transactions/Repay/RepayActions.tsx @@ -1,12 +1,9 @@ -import { InterestRate } from '@aave/contract-helpers'; +import { InterestRate, ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { utils } from 'ethers'; import { useTransactionHandler } from 'src/helpers/useTransactionHandler'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; -import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; import { useRootStore } from 'src/store/root'; -import { permitByChainAndToken } from 'src/ui-config/permitConfig'; import { TxActionsWrapper } from '../TxActionsWrapper'; @@ -34,15 +31,13 @@ export const RepayActions = ({ blocked, ...props }: RepayActionProps) => { - const { currentChainId: chainId, currentMarketData } = useProtocolDataContext(); - const repay = useRootStore((state) => state.repay); - const repayWithPermit = useRootStore((state) => state.repayWithPermit); + const { repay, repayWithPermit, tryPermit } = useRootStore(); + const usingPermit = tryPermit(poolAddress); const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } = useTransactionHandler({ - // move tryPermit to store - tryPermit: - currentMarketData.v3 && permitByChainAndToken[chainId]?.[utils.getAddress(poolAddress)], + tryPermit: usingPermit, + permitAction: ProtocolAction.repayWithPermit, handleGetTxns: async () => { return repay({ amountToRepay, @@ -85,9 +80,15 @@ export const RepayActions = ({ sx={sx} {...props} handleAction={action} - handleApproval={() => approval(amountToRepay, poolAddress)} + handleApproval={() => + approval({ + amount: amountToRepay, + underlyingAsset: poolAddress, + }) + } actionText={Repay {symbol}} actionInProgressText={Repaying {symbol}} + tryPermit={usingPermit} /> ); }; diff --git a/src/components/transactions/Stake/StakeActions.tsx b/src/components/transactions/Stake/StakeActions.tsx index 2d73172885..d092bcdf10 100644 --- a/src/components/transactions/Stake/StakeActions.tsx +++ b/src/components/transactions/Stake/StakeActions.tsx @@ -47,7 +47,7 @@ export const StakeActions = ({ isWrongNetwork={isWrongNetwork} amount={amountToStake} handleAction={action} - handleApproval={approval} + handleApproval={() => approval({ amount: amountToStake, underlyingAsset: selectedToken })} symbol={symbol} requiresAmount actionText={Stake} diff --git a/src/components/transactions/Supply/SupplyActions.tsx b/src/components/transactions/Supply/SupplyActions.tsx index 0c378a7ab7..f6ddcb3673 100644 --- a/src/components/transactions/Supply/SupplyActions.tsx +++ b/src/components/transactions/Supply/SupplyActions.tsx @@ -1,10 +1,8 @@ +import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; -import { utils } from 'ethers'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; -import { useProtocolDataContext } from 'src/hooks/useProtocolDataContext'; import { useRootStore } from 'src/store/root'; -import { permitByChainAndToken } from 'src/ui-config/permitConfig'; import { useTransactionHandler } from '../../../helpers/useTransactionHandler'; import { TxActionsWrapper } from '../TxActionsWrapper'; @@ -28,15 +26,13 @@ export const SupplyActions = ({ blocked, ...props }: SupplyActionProps) => { - const { currentChainId: chainId, currentMarketData } = useProtocolDataContext(); - const supply = useRootStore((state) => state.supply); - const supplyWithPermit = useRootStore((state) => state.supplyWithPermit); + const { supply, supplyWithPermit, tryPermit } = useRootStore(); + const usingPermit = tryPermit(poolAddress); const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } = useTransactionHandler({ - // TODO: move tryPermit - tryPermit: - currentMarketData.v3 && permitByChainAndToken[chainId]?.[utils.getAddress(poolAddress)], + tryPermit: usingPermit, + permitAction: ProtocolAction.supplyWithPermit, handleGetTxns: async () => { return supply({ amountToSupply, @@ -66,12 +62,19 @@ export const SupplyActions = ({ isWrongNetwork={isWrongNetwork} requiresAmount amount={amountToSupply} + symbol={symbol} preparingTransactions={loadingTxns} actionText={Supply {symbol}} actionInProgressText={Supplying {symbol}} - handleApproval={() => approval(amountToSupply, poolAddress)} + handleApproval={() => + approval({ + amount: amountToSupply, + underlyingAsset: poolAddress, + }) + } handleAction={action} requiresApproval={requiresApproval} + tryPermit={usingPermit} sx={sx} {...props} /> diff --git a/src/components/transactions/Swap/SwapActions.tsx b/src/components/transactions/Swap/SwapActions.tsx index 9a9fab1913..9ebce3317a 100644 --- a/src/components/transactions/Swap/SwapActions.tsx +++ b/src/components/transactions/Swap/SwapActions.tsx @@ -74,7 +74,9 @@ export const SwapActions = ({ handleAction={action} requiresAmount amount={amountToSwap} - handleApproval={() => approval(amountToSwap, poolReserve.aTokenAddress)} + handleApproval={() => + approval({ amount: amountToSwap, underlyingAsset: poolReserve.aTokenAddress }) + } requiresApproval={requiresApproval} actionText={Swap} actionInProgressText={Swapping} diff --git a/src/components/transactions/TxActionsWrapper.tsx b/src/components/transactions/TxActionsWrapper.tsx index 26c3f0b846..ac28047b65 100644 --- a/src/components/transactions/TxActionsWrapper.tsx +++ b/src/components/transactions/TxActionsWrapper.tsx @@ -1,12 +1,13 @@ +import { CheckIcon } from '@heroicons/react/solid'; import { Trans } from '@lingui/macro'; -import { Box, BoxProps, Button, CircularProgress, Typography } from '@mui/material'; +import { Box, BoxProps, Button, CircularProgress, SvgIcon, Typography } from '@mui/material'; import isEmpty from 'lodash/isEmpty'; import { ReactNode } from 'react'; import { TxStateType, useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { TxAction } from 'src/ui-config/errorMapping'; -import { LeftHelperText } from './FlowCommons/LeftHelperText'; +import { ApprovalTooltip } from '../infoTooltips/ApprovalTooltip'; import { RightHelperText } from './FlowCommons/RightHelperText'; interface TxActionsWrapperProps extends BoxProps { @@ -23,6 +24,7 @@ interface TxActionsWrapperProps extends BoxProps { requiresApproval: boolean; symbol?: string; blocked?: boolean; + tryPermit?: boolean; } export const TxActionsWrapper = ({ @@ -40,9 +42,10 @@ export const TxActionsWrapper = ({ sx, symbol, blocked, + tryPermit, ...rest }: TxActionsWrapperProps) => { - const { txError, retryWithApproval } = useModalContext(); + const { txError } = useModalContext(); const { watchModeOnlyAddress } = useWeb3Context(); const hasApprovalError = @@ -78,21 +81,40 @@ export const TxActionsWrapper = ({ return null; if (approvalTxState?.loading) return { loading: true, disabled: true, content: Approving {symbol}... }; - if (approvalTxState?.success) return { disabled: true, content: Approved }; - if (retryWithApproval) - return { content: Retry with approval, handleClick: handleApproval }; - return { content: Approve to continue, handleClick: handleApproval }; + if (approvalTxState?.success) + return { + disabled: true, + content: ( + <> + Approve Confirmed + + + + + ), + }; + + return { + content: ( + Approve {symbol} to continue} + /> + ), + handleClick: handleApproval, + }; } const { content, disabled, loading, handleClick } = getMainParams(); const approvalParams = getApprovalParams(); - return ( {requiresApproval && !watchModeOnlyAddress && ( - - - + + )} @@ -100,7 +122,7 @@ export const TxActionsWrapper = ({