Skip to content

Commit

Permalink
wip: initial prgression of permit2 ui
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture committed Sep 19, 2024
1 parent ed932ac commit 704938a
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,12 @@
"negativeRatio": "Insufficient sell amount"
}
},
"permit2": {
"title": "Permit token transfer",
"description": "Grant the smart contract permission to use %{symbol} on your behalf.",
"error": "A problem occurred signing Permit2 message",
"signMessage": "Sign message"
},
"errors": {
"title": "Something went wrong",
"amountTooSmall": "Minimum: %{minLimit}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const ApprovalResetStep = ({
? TransactionExecutionState.Failed
: TransactionExecutionState.Pending
case HopExecutionState.AwaitingApproval:
case HopExecutionState.AwaitingPermit2:
case HopExecutionState.AwaitingSwap:
case HopExecutionState.Complete:
return TransactionExecutionState.Complete
Expand Down Expand Up @@ -125,6 +126,7 @@ export const ApprovalResetStep = ({
const isComplete = useMemo(() => {
return [
HopExecutionState.AwaitingApproval,
HopExecutionState.AwaitingPermit2,
HopExecutionState.AwaitingSwap,
HopExecutionState.Complete,
].includes(hopExecutionState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const ApprovalStep = ({
return approval.state === TransactionExecutionState.Failed
? TransactionExecutionState.Failed
: TransactionExecutionState.Pending
case HopExecutionState.AwaitingPermit2:
case HopExecutionState.AwaitingSwap:
case HopExecutionState.Complete:
return TransactionExecutionState.Complete
Expand Down Expand Up @@ -148,7 +149,11 @@ export const ApprovalStep = ({
)

const isComplete = useMemo(() => {
return [HopExecutionState.AwaitingSwap, HopExecutionState.Complete].includes(hopExecutionState)
return [
HopExecutionState.AwaitingPermit2,
HopExecutionState.AwaitingSwap,
HopExecutionState.Complete,
].includes(hopExecutionState)
}, [hopExecutionState])

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { ApprovalStep } from './ApprovalStep'
import { AssetSummaryStep } from './AssetSummaryStep'
import { FeeStep } from './FeeStep'
import { HopTransactionStep } from './HopTransactionStep'
import { Permit2Step } from './Permit2Step/Permit2Step'
import { TimeRemaining } from './TimeRemaining'

const collapseWidth = {
Expand Down Expand Up @@ -97,6 +98,7 @@ export const Hop = ({
approval,
swap,
allowanceReset,
permit2,
} = useAppSelector(state => selectHopExecutionMetadata(state, hopExecutionMetadataFilter))

const isError = useMemo(
Expand Down Expand Up @@ -150,8 +152,10 @@ export const Hop = ({
return hopIndex === 0 ? 1 : 0
case HopExecutionState.AwaitingApproval:
return hopIndex === 0 ? 2 : 1
case HopExecutionState.AwaitingSwap:
case HopExecutionState.AwaitingPermit2:
return hopIndex === 0 ? 3 : 2
case HopExecutionState.AwaitingSwap:
return hopIndex === 0 ? 4 : 3
case HopExecutionState.Complete:
return Infinity
default:
Expand Down Expand Up @@ -187,6 +191,7 @@ export const Hop = ({
)
case HopExecutionState.AwaitingApprovalReset:
case HopExecutionState.AwaitingApproval:
case HopExecutionState.AwaitingPermit2:
case HopExecutionState.AwaitingSwap:
return (
<Circle size={8} bg='background.surface.raised.base'>
Expand Down Expand Up @@ -240,6 +245,15 @@ export const Hop = ({
/>
)}
</Collapse>
<Collapse in={permit2.isRequired} style={collapseWidth}>
{permit2.isRequired === true && (
<Permit2Step
tradeQuoteStep={tradeQuoteStep}
hopIndex={hopIndex}
activeTradeId={activeTradeId}
/>
)}
</Collapse>
<HopTransactionStep
swapperName={swapperName}
tradeQuoteStep={tradeQuoteStep}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { TradeQuote, TradeQuoteStep } from '@shapeshiftoss/swapper'
import { assertUnreachable } from '@shapeshiftoss/utils'
import { useMemo } from 'react'
import { FaThumbsUp } from 'react-icons/fa'
import { selectHopExecutionMetadata } from 'state/slices/tradeQuoteSlice/selectors'
import { HopExecutionState, TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types'
import { useAppSelector } from 'state/store'

import { ErrorMsg } from '../SharedApprovalStep/components/SharedApprovalDescription'
import { SharedApprovalStepComplete } from '../SharedApprovalStep/components/SharedApprovalStepComplete'
import { StatusIcon } from '../StatusIcon'
import { Permit2StepPending } from './components/Permit2StepPending'

export type Permit2StepProps = {
tradeQuoteStep: TradeQuoteStep
hopIndex: number
isLoading?: boolean
activeTradeId: TradeQuote['id']
}

const defaultIcon = <FaThumbsUp />

export const Permit2Step = ({
tradeQuoteStep,
hopIndex,
isLoading,
activeTradeId,
}: Permit2StepProps) => {
const hopExecutionMetadataFilter = useMemo(() => {
return {
tradeId: activeTradeId,
hopIndex,
}
}, [activeTradeId, hopIndex])
const { state: hopExecutionState, permit2 } = useAppSelector(state =>
selectHopExecutionMetadata(state, hopExecutionMetadataFilter),
)

const stepIndicator = useMemo(() => {
const txStatus = (() => {
switch (hopExecutionState) {
case HopExecutionState.Pending:
case HopExecutionState.AwaitingApprovalReset:
case HopExecutionState.AwaitingApproval:
return TransactionExecutionState.AwaitingConfirmation
case HopExecutionState.AwaitingPermit2:
return permit2.state === TransactionExecutionState.Failed
? TransactionExecutionState.Failed
: TransactionExecutionState.Pending
case HopExecutionState.AwaitingSwap:
case HopExecutionState.Complete:
return TransactionExecutionState.Complete
default:
assertUnreachable(hopExecutionState)
}
})()

return <StatusIcon txStatus={txStatus} defaultIcon={defaultIcon} />
}, [hopExecutionState, permit2.state])

const completedDescription = useMemo(() => {
return (
<ErrorMsg
isError={permit2.state === TransactionExecutionState.Failed}
errorTranslation={'trade.permit2.error'}
/>
)
}, [permit2.state])

const isComplete = useMemo(() => {
return [HopExecutionState.AwaitingSwap, HopExecutionState.Complete].includes(hopExecutionState)
}, [hopExecutionState])

// separate component for completed states to simplify hook dismount
return isComplete ? (
<SharedApprovalStepComplete
titleTranslation={'trade.permit2.title'}
isLoading={isLoading}
transactionExecutionState={permit2.state}
description={completedDescription}
stepIndicator={stepIndicator}
/>
) : (
<Permit2StepPending
tradeQuoteStep={tradeQuoteStep}
hopIndex={hopIndex}
isLoading={isLoading}
activeTradeId={activeTradeId}
stepIndicator={stepIndicator}
hopExecutionState={hopExecutionState}
transactionExecutionState={permit2.state}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Button, Card, CircularProgress, VStack } from '@chakra-ui/react'
import type { TradeQuoteStep } from '@shapeshiftoss/swapper'
import { useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'
import { HopExecutionState, TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types'

import { StepperStep } from '../../StepperStep'

export type Permit2StepPendingProps = {
tradeQuoteStep: TradeQuoteStep
hopIndex: number
isLoading?: boolean
activeTradeId: string
hopExecutionState: HopExecutionState
transactionExecutionState: TransactionExecutionState
stepIndicator: JSX.Element
}

export const Permit2StepPending = ({
tradeQuoteStep,
hopIndex,
isLoading,
activeTradeId,
stepIndicator,
hopExecutionState,
transactionExecutionState,
}: Permit2StepPendingProps) => {
const translate = useTranslate()

const isPermit2MutationLoading = false
// const { permit2Mutation, isLoading: isPermit2MutationLoading } = usePermit2Mutation(
// tradeQuoteStep,
// hopIndex,
// allowanceType,
// true,
// activeTradeId,
// )

const handleSignPermit2Message = useCallback(() => {
try {
// TODO:
// await permit2Mutation.mutateAsync()
} catch (error) {
console.error(error)
}
}, [])

const content = useMemo(() => {
const isAwaitingPermit2 = hopExecutionState === HopExecutionState.AwaitingPermit2

// only render the permit2 button when the component is active
if (!isAwaitingPermit2) return

const isDisabled =
isPermit2MutationLoading ||
!isAwaitingPermit2 ||
transactionExecutionState !== TransactionExecutionState.AwaitingConfirmation

return (
<Card p='2' width='full'>
<VStack width='full'>
<Button
width='full'
size='sm'
colorScheme='blue'
isDisabled={isDisabled}
isLoading={isPermit2MutationLoading}
onClick={handleSignPermit2Message}
>
{transactionExecutionState !== TransactionExecutionState.AwaitingConfirmation && (
<CircularProgress isIndeterminate size={2} mr={2} />
)}
{translate('trade.permit2.signMessage')}
</Button>
</VStack>
</Card>
)
}, [
handleSignPermit2Message,
hopExecutionState,
isPermit2MutationLoading,
transactionExecutionState,
translate,
])

return (
<StepperStep
title={translate('trade.permit2.title')}
description={translate('trade.permit2.description', {
symbol: tradeQuoteStep.sellAsset.symbol,
})}
stepIndicator={stepIndicator}
content={content}
isLastStep={false}
isLoading={isLoading}
isError={transactionExecutionState === TransactionExecutionState.Failed}
isPending={transactionExecutionState === TransactionExecutionState.Pending}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { getTxLink } from 'lib/getTxLink'
import { selectFirstHopSellAccountId } from 'state/slices/selectors'
import { useAppSelector } from 'state/store'

type ErrorMsgProps = {
export type ErrorMsgProps = {
isError: boolean
errorTranslation: string
}

const ErrorMsg = ({ isError, errorTranslation }: ErrorMsgProps) => {
export const ErrorMsg = ({ isError, errorTranslation }: ErrorMsgProps) => {
return isError ? (
<Text color='text.error' translation={errorTranslation} fontWeight='bold' />
) : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ export const useIsApprovalInitiallyNeeded = () => {
secondHop: isAllowanceResetNeededForSecondHop ?? false,
}),
)

// TODO: actually wire this up
dispatch(
tradeQuoteSlice.actions.setPermit2Requirements({
id: activeQuote.id,
firstHop: true,
secondHop: true,
}),
)
}, [
activeQuote?.id,
dispatch,
Expand Down
1 change: 1 addition & 0 deletions src/state/slices/tradeQuoteSlice/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const initialHopState = {
state: HopExecutionState.Pending,
allowanceReset: initialTransactionState,
approval: initialTransactionState,
permit2: initialTransactionState,
swap: initialTransactionState,
}

Expand Down
8 changes: 8 additions & 0 deletions src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ export const tradeQuoteSlice = createSlice({
state.tradeExecution[action.payload.id].secondHop.allowanceReset.isRequired =
action.payload?.secondHop
},
setPermit2Requirements: (
state,
action: PayloadAction<{ firstHop: boolean; secondHop: boolean; id: TradeQuote['id'] }>,
) => {
state.tradeExecution[action.payload.id].firstHop.permit2.isRequired = action.payload?.firstHop
state.tradeExecution[action.payload.id].secondHop.permit2.isRequired =
action.payload?.secondHop
},
setApprovalTxHash: (
state,
action: PayloadAction<{
Expand Down
2 changes: 2 additions & 0 deletions src/state/slices/tradeQuoteSlice/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum HopExecutionState {
Pending = 'Pending',
AwaitingApprovalReset = 'AwaitingApprovalReset',
AwaitingApproval = 'AwaitingApproval',
AwaitingPermit2 = 'AwaitingPermit2',
AwaitingSwap = 'AwaitingSwap',
Complete = 'Complete',
}
Expand Down Expand Up @@ -77,6 +78,7 @@ export type HopExecutionMetadata = {
state: HopExecutionState
allowanceReset: ApprovalExecutionMetadata
approval: ApprovalExecutionMetadata
permit2: Omit<ApprovalExecutionMetadata, 'txHash'>
swap: SwapExecutionMetadata
}

Expand Down

0 comments on commit 704938a

Please sign in to comment.