diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 055fe7d2d3b..7cbd7e4ac79 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -923,12 +923,16 @@ "cta": "Ok, got it" } }, - "permit2": { - "title": "Permit token transfer:", + "permit2Allowance": { + "title": "Permit2 contract allowance", + "tooltip": "This trade uses Permit2, which requires a one-time approval to grant unlimited allowance to the Permit2 contract. After this initial setup, you can authorize trades on any Permit2-supported protocol by signing gas-free messages that grant temporary 5-minute allowances." + }, + "permit2Eip712": { + "title": "Permit token transfer", "description": "Grant the smart contract permission to use %{symbol} on your behalf.", - "tooltip": "This trade has an added security feature called 'Permit2' which requires you to grant permission for the application to complete the trade on your behalf.", "error": "A problem occurred signing Permit2 message", - "signMessage": "Sign message" + "signMessage": "Sign message", + "tooltip": "This will grant %{swapperName} a 5-minute allowance to complete the trade on your behalf." }, "errors": { "title": "Something went wrong", @@ -1013,7 +1017,6 @@ "swap": "Swap on %{sellChainName} via %{swapperName}" }, "approvalTitle": "Token allowance approval", - "permit2Title": "Permit token transfer", "resetTitle": "Token allowance reset", "fiatAmountOnChain": "%{amountFiatFormatted} on %{chainName}", "quote": { diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/ApprovalStep.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/ApprovalStep.tsx index 69600dad98f..bf781122d94 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/ApprovalStep.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/ApprovalStep.tsx @@ -90,7 +90,7 @@ export const ApprovalStep = ({ allowanceApproval.state === TransactionExecutionState.Complete ? TransactionExecutionState.AwaitingConfirmation : allowanceApproval.state - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: return permit2.state case HopExecutionState.AwaitingSwap: case HopExecutionState.Complete: @@ -118,7 +118,7 @@ export const ApprovalStep = ({ return allowanceResetContent case HopExecutionState.AwaitingAllowanceApproval: return allowanceApprovalContent - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: return permit2Content case HopExecutionState.AwaitingSwap: case HopExecutionState.Complete: @@ -172,7 +172,7 @@ export const ApprovalStep = ({ return allowanceResetDescription case HopExecutionState.AwaitingAllowanceApproval: return allowanceApprovalDescription - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: return permit2Description case HopExecutionState.AwaitingSwap: case HopExecutionState.Complete: @@ -213,7 +213,7 @@ export const ApprovalStep = ({ allowanceReset.state === TransactionExecutionState.AwaitingConfirmation) || (hopExecutionState === HopExecutionState.AwaitingAllowanceApproval && allowanceApproval.state === TransactionExecutionState.AwaitingConfirmation) || - (hopExecutionState === HopExecutionState.AwaitingPermit2 && + (hopExecutionState === HopExecutionState.AwaitingPermit2Eip712Sign && permit2.state === TransactionExecutionState.AwaitingConfirmation) } /> diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/components/ApprovalContent.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/components/ApprovalContent.tsx index e99bfb9b3c1..b0482a5fcb4 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/components/ApprovalContent.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/components/ApprovalContent.tsx @@ -1,4 +1,13 @@ -import { Box, Button, Card, CircularProgress, Icon, Tooltip, VStack } from '@chakra-ui/react' +import { + Box, + Button, + Card, + CircularProgress, + Icon, + Text as CText, + Tooltip, + VStack, +} from '@chakra-ui/react' import type { InterpolationOptions } from 'node-polyglot' import { FaInfoCircle } from 'react-icons/fa' import { useTranslate } from 'react-polyglot' @@ -12,7 +21,7 @@ export type ApprovalContentProps = { isLoading: boolean subHeadingTranslation?: string | [string, InterpolationOptions] titleTranslation: string - tooltipTranslation: string + tooltipTranslation: string | [string, InterpolationOptions] topRightContent?: JSX.Element transactionExecutionState: TransactionExecutionState onSubmit: () => void @@ -39,7 +48,13 @@ export const ApprovalContent = ({ - + @@ -54,7 +69,11 @@ export const ApprovalContent = ({ {subHeadingTranslation && ( - + + {typeof subHeadingTranslation === 'string' + ? translate(subHeadingTranslation) + : translate(...subHeadingTranslation)} + )} diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/useAllowanceApprovalContent.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/useAllowanceApprovalContent.tsx index cce6e9340db..73c8c044d3f 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/useAllowanceApprovalContent.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/useAllowanceApprovalContent.tsx @@ -126,7 +126,7 @@ export const useAllowanceApprovalContent = ({ isDisabled={isApprovalButtonDisabled} isLoading={isAllowanceApprovalLoading} titleTranslation='trade.allowance' - tooltipTranslation={isPermit2 ? 'trade.permit2.tooltip' : 'trade.allowanceTooltip'} + tooltipTranslation={isPermit2 ? 'trade.permit2Allowance.tooltip' : 'trade.allowanceTooltip'} transactionExecutionState={transactionExecutionState} onSubmit={handleSignAllowanceApproval} topRightContent={ diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/usePermit2Content.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/usePermit2Content.tsx index 7d55639784a..338cb59346e 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/usePermit2Content.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep/hooks/usePermit2Content.tsx @@ -3,7 +3,10 @@ import { isSome } from '@shapeshiftoss/utils' import type { InterpolationOptions } from 'node-polyglot' import { useMemo } from 'react' import { useGetTradeQuotes } from 'components/MultiHopTrade/hooks/useGetTradeQuotes/useGetTradeQuotes' -import { selectHopExecutionMetadata } from 'state/slices/tradeQuoteSlice/selectors' +import { + selectActiveSwapperName, + selectHopExecutionMetadata, +} from 'state/slices/tradeQuoteSlice/selectors' import { HopExecutionState, TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types' import { useAppSelector } from 'state/store' @@ -37,12 +40,14 @@ export const usePermit2Content = ({ allowanceApproval, } = useAppSelector(state => selectHopExecutionMetadata(state, hopExecutionMetadataFilter)) + const swapperName = useAppSelector(selectActiveSwapperName) + const { signPermit2 } = useSignPermit2(tradeQuoteStep, hopIndex, activeTradeId) const { isLoading: isTradeQuotesLoading } = useGetTradeQuotes() const isButtonDisabled = useMemo(() => { - const isAwaitingPermit2 = hopExecutionState === HopExecutionState.AwaitingPermit2 + const isAwaitingPermit2 = hopExecutionState === HopExecutionState.AwaitingPermit2Eip712Sign const isError = permit2.state === TransactionExecutionState.Failed const isAwaitingConfirmation = permit2.state === TransactionExecutionState.AwaitingConfirmation const isDisabled = @@ -52,22 +57,26 @@ export const usePermit2Content = ({ }, [hopExecutionState, permit2.state, isTradeQuotesLoading]) const subHeadingTranslation: [string, InterpolationOptions] = useMemo(() => { - return ['trade.permit2.description', { symbol: tradeQuoteStep.sellAsset.symbol }] + return ['trade.permit2Eip712.description', { symbol: tradeQuoteStep.sellAsset.symbol }] }, [tradeQuoteStep]) + const tooltipTranslation: [string, InterpolationOptions] = useMemo(() => { + return ['trade.permit2Eip712.tooltip', { swapperName }] + }, [swapperName]) + const content = useMemo(() => { - if (hopExecutionState !== HopExecutionState.AwaitingPermit2) return + if (hopExecutionState !== HopExecutionState.AwaitingPermit2Eip712Sign) return return ( @@ -79,6 +88,7 @@ export const usePermit2Content = ({ permit2.state, signPermit2, subHeadingTranslation, + tooltipTranslation, ]) const description = useMemo(() => { @@ -98,7 +108,7 @@ export const usePermit2Content = ({ tradeQuoteStep={tradeQuoteStep} txLines={txLines} isError={permit2.state === TransactionExecutionState.Failed} - errorTranslation='trade.permit2.error' + errorTranslation='trade.permit2Eip712.error' /> ) }, [allowanceApproval.txHash, allowanceReset.txHash, permit2.state, tradeQuoteStep]) diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx index 35892e5de91..fff206659a9 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx @@ -187,7 +187,7 @@ export const Hop = ({ // fallthrough case HopExecutionState.AwaitingAllowanceApproval: // fallthrough - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: return hopIndex === 0 ? 1 : 0 case HopExecutionState.AwaitingSwap: return hopIndex === 0 ? 2 : 1 @@ -226,7 +226,7 @@ export const Hop = ({ ) case HopExecutionState.AwaitingAllowanceReset: case HopExecutionState.AwaitingAllowanceApproval: - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: case HopExecutionState.AwaitingSwap: return ( diff --git a/src/components/MultiHopTrade/components/TradeConfirm/ExpandedStepperSteps.tsx b/src/components/MultiHopTrade/components/TradeConfirm/ExpandedStepperSteps.tsx index 4fccf15d7cb..0eace6f3c86 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/ExpandedStepperSteps.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/ExpandedStepperSteps.tsx @@ -26,7 +26,7 @@ import { selectActiveQuoteErrors, selectHopExecutionMetadata, } from 'state/slices/tradeQuoteSlice/selectors' -import { TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types' +import { HopExecutionState, TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types' import { useAppSelector, useSelectorWithArgs } from 'state/store' import { StepperStep as StepperStepComponent } from '../MultiHopTradeConfirm/components/StepperStep' @@ -120,6 +120,7 @@ export const ExpandedStepperSteps = ({ activeTradeQuote }: ExpandedStepperStepsP }, [activeTradeId]) const { + state: hopExecutionState, allowanceApproval: firstHopAllowanceApproval, permit2: firstHopPermit2, allowanceReset: firstHopAllowanceReset, @@ -194,40 +195,65 @@ export const ExpandedStepperSteps = ({ activeTradeQuote }: ExpandedStepperStepsP }, [firstHopAllowanceReset.txHash, firstHopSellAccountId, tradeQuoteFirstHop]) const firstHopAllowanceApprovalTitle = useMemo(() => { - return ( - - {firstHopPermit2.isRequired === true ? ( + const content = (() => { + // Awaiting Permit2 contract allowance grant + if ( + firstHopPermit2.isRequired && + hopExecutionState === HopExecutionState.AwaitingAllowanceApproval + ) + return ( <> - - + + - ) : ( - <> - - {firstHopAllowanceApproval.txHash && tradeQuoteFirstHop && firstHopSellAccountId && ( - - )} - - )} + ) + + // Good ol' allowances + return ( + <> + + {firstHopAllowanceApproval.txHash && tradeQuoteFirstHop && firstHopSellAccountId && ( + + )} + + ) + })() + return ( + + {content} ) }, [ firstHopAllowanceApproval.txHash, firstHopPermit2.isRequired, firstHopSellAccountId, + hopExecutionState, tradeQuoteFirstHop, translate, ]) + const firstHopPermit2SignTitle = useMemo(() => { + return ( + + + + + + + + + ) + }, [swapperName, translate]) + const firstHopActionTitle = useMemo(() => { return ( @@ -296,8 +322,8 @@ export const ExpandedStepperSteps = ({ activeTradeQuote }: ExpandedStepperStepsP {lastHopPermit2.isRequired === true ? ( <> - - + + @@ -426,6 +452,16 @@ export const ExpandedStepperSteps = ({ activeTradeQuote }: ExpandedStepperStepsP stepIndicatorVariant='innerSteps' /> ) : null} + {tradeSteps[StepperStep.FirstHopPermit2Eip712Sign] ? ( + + ) : null} & { + firstHopPermit2Eip712: Omit & { permit2Signature?: string | undefined } firstHopAllowanceReset: ApprovalExecutionMetadata @@ -60,6 +60,7 @@ type StepperStepParams = { export enum StepperStep { FirstHopReset = 'firstHopReset', + FirstHopPermit2Eip712Sign = 'firstHopPermit2Eip712Sign', FirstHopApproval = 'firstHopApproval', FirstHopSwap = 'firstHopSwap', LastHopReset = 'lastHopReset', @@ -72,7 +73,7 @@ export const getStepperSteps = (params: StepperStepParams): Record { } const isInApprovalState = (state: HopExecutionState): boolean => { - return [HopExecutionState.AwaitingAllowanceApproval, HopExecutionState.AwaitingPermit2].includes( - state, - ) + return state === HopExecutionState.AwaitingAllowanceApproval +} + +const isInPermit2SignState = (state: HopExecutionState): boolean => { + return state === HopExecutionState.AwaitingPermit2Eip712Sign } export const getCurrentStepperStep = ( @@ -122,6 +124,7 @@ export const getCurrentStepperStep = ( if (hopExecutionState === HopExecutionState.AwaitingAllowanceReset) return StepperStep.FirstHopReset if (isInApprovalState(hopExecutionState)) return StepperStep.FirstHopApproval + if (isInPermit2SignState(hopExecutionState)) return StepperStep.FirstHopPermit2Eip712Sign if (hopExecutionState === HopExecutionState.AwaitingSwap) return StepperStep.FirstHopSwap } else if (currentHopIndex === 1) { if (hopExecutionState === HopExecutionState.AwaitingAllowanceReset) diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useStepperSteps.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useStepperSteps.tsx index 040b974b3a3..b89970e0f88 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useStepperSteps.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useStepperSteps.tsx @@ -29,7 +29,7 @@ export const useStepperSteps = () => { const { state: firstHopExecutionState, allowanceApproval: firstHopAllowanceApproval, - permit2: firstHopPermit2, + permit2: firstHopPermit2Eip712, allowanceReset: firstHopAllowanceReset, } = useAppSelector(state => selectHopExecutionMetadata(state, firstHopExecutionMetadataFilter)) @@ -51,7 +51,7 @@ export const useStepperSteps = () => { const params = useMemo( () => ({ firstHopAllowanceApproval, - firstHopPermit2, + firstHopPermit2Eip712, firstHopAllowanceReset, lastHopAllowanceApproval, lastHopPermit2, @@ -60,7 +60,7 @@ export const useStepperSteps = () => { }), [ firstHopAllowanceApproval, - firstHopPermit2, + firstHopPermit2Eip712, firstHopAllowanceReset, lastHopAllowanceApproval, lastHopPermit2, diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx index 4e71174a6b8..409f2603f23 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx @@ -117,7 +117,7 @@ export const useTradeButtonProps = ({ isLoading: isAllowanceApprovalPending, isDisabled: isAllowanceApprovalLoading, } - case HopExecutionState.AwaitingPermit2: + case HopExecutionState.AwaitingPermit2Eip712Sign: return { onSubmit: signPermit2, buttonText, diff --git a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts index b39492781f9..c8568c00016 100644 --- a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts +++ b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts @@ -96,7 +96,8 @@ export const tradeQuoteSlice = createSlice({ HopExecutionState.AwaitingAllowanceApproval break case permit2Required: - state.tradeExecution[tradeQuoteId].firstHop.state = HopExecutionState.AwaitingPermit2 + state.tradeExecution[tradeQuoteId].firstHop.state = + HopExecutionState.AwaitingPermit2Eip712Sign break default: state.tradeExecution[tradeQuoteId].firstHop.state = HopExecutionState.AwaitingSwap @@ -219,7 +220,7 @@ export const tradeQuoteSlice = createSlice({ const permit2Required = state.tradeExecution[id][key].permit2.isRequired state.tradeExecution[id][key].state = permit2Required - ? HopExecutionState.AwaitingPermit2 + ? HopExecutionState.AwaitingPermit2Eip712Sign : HopExecutionState.AwaitingSwap }, setSwapTxPending: ( diff --git a/src/state/slices/tradeQuoteSlice/types.ts b/src/state/slices/tradeQuoteSlice/types.ts index 567ffd830ce..7cecfd2874c 100644 --- a/src/state/slices/tradeQuoteSlice/types.ts +++ b/src/state/slices/tradeQuoteSlice/types.ts @@ -26,7 +26,7 @@ export enum HopExecutionState { Pending = 'Pending', AwaitingAllowanceReset = 'AwaitingAllowanceReset', AwaitingAllowanceApproval = 'AwaitingAllowanceApproval', - AwaitingPermit2 = 'AwaitingPermit2', + AwaitingPermit2Eip712Sign = 'AwaitingPermit2Eip712Sign', AwaitingSwap = 'AwaitingSwap', Complete = 'Complete', }