diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalResetStep.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalResetStep.tsx
index 5529105284e..bbf0530fc6d 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalResetStep.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalResetStep.tsx
@@ -9,6 +9,7 @@ import {
VStack,
} from '@chakra-ui/react'
import type { TradeQuote, TradeQuoteStep } from '@shapeshiftoss/swapper'
+import { assertUnreachable } from '@shapeshiftoss/utils'
import { useCallback, useMemo } from 'react'
import { FaInfoCircle } from 'react-icons/fa'
import { FaRotateRight } from 'react-icons/fa6'
@@ -17,12 +18,12 @@ import { Row } from 'components/Row/Row'
import { Text } from 'components/Text'
import { AllowanceType } from 'hooks/queries/useApprovalFees'
import { selectHopExecutionMetadata } from 'state/slices/tradeQuoteSlice/selectors'
-import { TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types'
+import { HopExecutionState, TransactionExecutionState } from 'state/slices/tradeQuoteSlice/types'
import { useAppSelector } from 'state/store'
import { SharedApprovalStep } from './SharedApprovalStep/SharedApprovalStep'
import type { RenderAllowanceContentCallbackParams } from './SharedApprovalStep/types'
-import { ApprovalStatusIcon } from './StatusIcon'
+import { StatusIcon } from './StatusIcon'
export type ApprovalResetStepProps = {
tradeQuoteStep: TradeQuoteStep
@@ -33,7 +34,7 @@ export type ApprovalResetStepProps = {
activeTradeId: TradeQuote['id']
}
-const initialIcon =
+const defaultIcon =
export const ApprovalResetStep = ({
tradeQuoteStep,
@@ -50,19 +51,30 @@ export const ApprovalResetStep = ({
hopIndex,
}
}, [activeTradeId, hopIndex])
- const { state, allowanceReset } = useAppSelector(state =>
+ const { state: hopExecutionState, allowanceReset } = useAppSelector(state =>
selectHopExecutionMetadata(state, hopExecutionMetadataFilter),
)
const stepIndicator = useMemo(() => {
- return (
-
- )
- }, [allowanceReset.state, state])
+ const txStatus = (() => {
+ switch (hopExecutionState) {
+ case HopExecutionState.Pending:
+ return TransactionExecutionState.AwaitingConfirmation
+ case HopExecutionState.AwaitingApprovalReset:
+ return allowanceReset.state === TransactionExecutionState.Failed
+ ? TransactionExecutionState.Failed
+ : TransactionExecutionState.Pending
+ case HopExecutionState.AwaitingApproval:
+ case HopExecutionState.AwaitingSwap:
+ case HopExecutionState.Complete:
+ return TransactionExecutionState.Complete
+ default:
+ assertUnreachable(hopExecutionState)
+ }
+ })()
+
+ return
+ }, [allowanceReset.state, hopExecutionState])
const renderResetAllowanceContent = useCallback(
({
@@ -110,13 +122,23 @@ export const ApprovalResetStep = ({
[translate, isActive],
)
+ const isComplete = useMemo(() => {
+ return [
+ HopExecutionState.AwaitingApproval,
+ HopExecutionState.AwaitingSwap,
+ HopExecutionState.Complete,
+ ].includes(hopExecutionState)
+ }, [hopExecutionState])
+
return (
)
}
diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep.tsx
index 49a8ebfb5da..305e343b2aa 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/ApprovalStep.tsx
@@ -9,9 +9,10 @@ import {
VStack,
} from '@chakra-ui/react'
import type { TradeQuote, TradeQuoteStep } from '@shapeshiftoss/swapper'
+import { assertUnreachable } from '@shapeshiftoss/utils'
import { useCallback, useMemo } from 'react'
import { FaInfoCircle } from 'react-icons/fa'
-import { FaRotateRight } from 'react-icons/fa6'
+import { FaThumbsUp } from 'react-icons/fa6'
import { useTranslate } from 'react-polyglot'
import { Row } from 'components/Row/Row'
import { Text } from 'components/Text'
@@ -23,7 +24,7 @@ import { useAppSelector } from 'state/store'
import { SharedApprovalStep } from './SharedApprovalStep/SharedApprovalStep'
import type { RenderAllowanceContentCallbackParams } from './SharedApprovalStep/types'
-import { ApprovalStatusIcon } from './StatusIcon'
+import { StatusIcon } from './StatusIcon'
export type ApprovalStepProps = {
tradeQuoteStep: TradeQuoteStep
@@ -34,7 +35,7 @@ export type ApprovalStepProps = {
activeTradeId: TradeQuote['id']
}
-const initialIcon =
+const defaultIcon =
export const ApprovalStep = ({
tradeQuoteStep,
@@ -53,22 +54,32 @@ export const ApprovalStep = ({
hopIndex,
}
}, [activeTradeId, hopIndex])
- const { state, approval } = useAppSelector(state =>
+ const { state: hopExecutionState, approval } = useAppSelector(state =>
selectHopExecutionMetadata(state, hopExecutionMetadataFilter),
)
const stepIndicator = useMemo(() => {
- return (
-
- )
- }, [approval.state, state])
+ const txStatus = (() => {
+ switch (hopExecutionState) {
+ case HopExecutionState.Pending:
+ case HopExecutionState.AwaitingApprovalReset:
+ return TransactionExecutionState.AwaitingConfirmation
+ case HopExecutionState.AwaitingApproval:
+ return approval.state === TransactionExecutionState.Failed
+ ? TransactionExecutionState.Failed
+ : TransactionExecutionState.Pending
+ case HopExecutionState.AwaitingSwap:
+ case HopExecutionState.Complete:
+ return TransactionExecutionState.Complete
+ default:
+ assertUnreachable(hopExecutionState)
+ }
+ })()
- const renderResetAllowanceContent = useCallback(
+ return
+ }, [approval.state, hopExecutionState])
+
+ const renderApprovalContent = useCallback(
({
hopExecutionState,
transactionExecutionState,
@@ -136,22 +147,27 @@ export const ApprovalStep = ({
[isActive, translate, isExactAllowance, toggleIsExactAllowance],
)
+ const isComplete = useMemo(() => {
+ return [HopExecutionState.AwaitingSwap, HopExecutionState.Complete].includes(hopExecutionState)
+ }, [hopExecutionState])
+
return (
)
}
diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx
index a6504c24c6c..714fba76d98 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/Hop.tsx
@@ -221,7 +221,7 @@ export const Hop = ({
)}
- {allowanceReset.isRequired && (
+ {allowanceReset.isRequired === true && (
{
- const isComplete = useMemo(() => {
- return [
- HopExecutionState.AwaitingApproval,
- HopExecutionState.AwaitingSwap,
- HopExecutionState.Complete,
- ].includes(hopExecutionState)
- }, [hopExecutionState])
-
const completedDescription = useMemo(() => {
return (
{
+ (approvalNetworkFeeCryptoFormatted: string) => {
return (
) : (
)
diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/SharedApprovalStep/components/SharedApprovalStepPending.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/SharedApprovalStep/components/SharedApprovalStepPending.tsx
index db1f71b20d4..5b9fb15d052 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/SharedApprovalStep/components/SharedApprovalStepPending.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/SharedApprovalStep/components/SharedApprovalStepPending.tsx
@@ -1,5 +1,5 @@
import type { TradeQuoteStep } from '@shapeshiftoss/swapper'
-import { useCallback, useMemo, useState } from 'react'
+import { useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'
import type { AllowanceType } from 'hooks/queries/useApprovalFees'
import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter'
@@ -14,6 +14,7 @@ import { StepperStep } from '../../StepperStep'
import type { RenderAllowanceContentCallback } from '../types'
export type SharedApprovalStepPendingProps = {
+ titleTranslation: string
tradeQuoteStep: TradeQuoteStep
hopIndex: number
isLoading?: boolean
@@ -22,12 +23,12 @@ export type SharedApprovalStepPendingProps = {
transactionExecutionState: TransactionExecutionState
stepIndicator: JSX.Element
allowanceType: AllowanceType
- feeQueryEnabled: boolean
- renderDescription: (approvalNetworkFeeCryptoFormatted?: string) => JSX.Element
+ renderDescription: (approvalNetworkFeeCryptoFormatted: string) => JSX.Element
renderContent: RenderAllowanceContentCallback
}
export const SharedApprovalStepPending = ({
+ titleTranslation,
tradeQuoteStep,
hopIndex,
isLoading,
@@ -36,36 +37,25 @@ export const SharedApprovalStepPending = ({
hopExecutionState,
transactionExecutionState,
allowanceType,
- feeQueryEnabled,
renderDescription,
renderContent,
}: SharedApprovalStepPendingProps) => {
+ const translate = useTranslate()
const {
number: { toCrypto },
} = useLocaleFormatter()
- const [localFeeQueryEnabled, setFeeQueryEnabled] = useState(true)
-
const {
approveMutation,
approvalNetworkFeeCryptoBaseUnit,
isLoading: isAllowanceApprovalLoading,
- } = useAllowanceApproval(
- tradeQuoteStep,
- hopIndex,
- allowanceType,
- feeQueryEnabled && localFeeQueryEnabled,
- activeTradeId,
- )
+ } = useAllowanceApproval(tradeQuoteStep, hopIndex, allowanceType, true, activeTradeId)
const handleSignAllowanceApproval = useCallback(async () => {
try {
- setFeeQueryEnabled(false)
await approveMutation.mutateAsync()
} catch (error) {
console.error(error)
- } finally {
- setFeeQueryEnabled(true)
}
}, [approveMutation])
@@ -74,19 +64,15 @@ export const SharedApprovalStepPending = ({
)
const approvalNetworkFeeCryptoFormatted = useMemo(() => {
- if (!feeAsset) return ''
-
- if (approvalNetworkFeeCryptoBaseUnit) {
+ if (feeAsset && approvalNetworkFeeCryptoBaseUnit) {
return toCrypto(
fromBaseUnit(approvalNetworkFeeCryptoBaseUnit, feeAsset.precision),
feeAsset.symbol,
)
}
- return ''
- }, [approvalNetworkFeeCryptoBaseUnit, feeAsset, toCrypto])
-
- const translate = useTranslate()
+ return translate('common.loadingText').toLowerCase()
+ }, [approvalNetworkFeeCryptoBaseUnit, feeAsset, toCrypto, translate])
const content = useMemo(() => {
return renderContent({
@@ -110,7 +96,7 @@ export const SharedApprovalStepPending = ({
return (
{
- const txStatus = useMemo(() => {
- switch (hopExecutionState) {
- case HopExecutionState.Pending:
- return TransactionExecutionState.AwaitingConfirmation
- case HopExecutionState.AwaitingApprovalReset:
- case HopExecutionState.AwaitingApproval:
- // override completed state to pending, isApprovalNeeded dictates this
- if (
- approvalTxState === TransactionExecutionState.Complete &&
- overrideCompletedStateToPending
- ) {
- return TransactionExecutionState.Pending
- }
-
- return approvalTxState
- // override approvalTxState if external approval triggered app to proceed to next step
- case HopExecutionState.AwaitingSwap:
- case HopExecutionState.Complete:
- return TransactionExecutionState.Complete
- default:
- assertUnreachable(hopExecutionState)
- }
- }, [hopExecutionState, approvalTxState, overrideCompletedStateToPending])
- return
-}
diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useAllowanceApproval.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useAllowanceApproval.tsx
index 552289ada06..ade3992bf52 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useAllowanceApproval.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useAllowanceApproval.tsx
@@ -35,7 +35,12 @@ export const useAllowanceApproval = (
const isReset = useMemo(() => allowanceType === AllowanceType.Reset, [allowanceType])
- const { allowanceCryptoBaseUnitResult, evmFeesResult, isApprovalRequired } = useApprovalFees({
+ const {
+ allowanceCryptoBaseUnitResult,
+ evmFeesResult,
+ isApprovalRequired,
+ isAllowanceResetRequired,
+ } = useApprovalFees({
amountCryptoBaseUnit: tradeQuoteStep.sellAmountIncludingProtocolFeesCryptoBaseUnit,
assetId: tradeQuoteStep.sellAsset.assetId,
from: sellAssetAccountId ? fromAccountId(sellAssetAccountId).account : undefined,
@@ -53,6 +58,15 @@ export const useAllowanceApproval = (
dispatch(tradeQuoteSlice.actions.setApprovalStepComplete({ hopIndex, id: confirmedTradeId }))
}, [dispatch, hopIndex, isApprovalRequired, confirmedTradeId])
+ useEffect(() => {
+ if (isAllowanceResetRequired !== false || allowanceType !== AllowanceType.Reset) return
+
+ // Mark the allowance reset step complete as required.
+ // This is deliberately disjoint to the approval transaction orchestration to allow users to
+ // complete an approval reset externally and have the app respond to the updated allowance on chain.
+ dispatch(tradeQuoteSlice.actions.setApprovalResetComplete({ hopIndex, id: confirmedTradeId }))
+ }, [dispatch, hopIndex, isAllowanceResetRequired, confirmedTradeId, allowanceType])
+
const approveMutation = useMutation({
...reactQueries.mutations.approve({
accountNumber: tradeQuoteStep.accountNumber,
diff --git a/src/hooks/queries/useApprovalFees.ts b/src/hooks/queries/useApprovalFees.ts
index 1bbb38be6f2..f903844d837 100644
--- a/src/hooks/queries/useApprovalFees.ts
+++ b/src/hooks/queries/useApprovalFees.ts
@@ -35,12 +35,13 @@ export const useApprovalFees = ({
return fromAssetId(assetId)
}, [assetId])
- const { allowanceCryptoBaseUnitResult, isApprovalRequired } = useIsApprovalRequired({
- amountCryptoBaseUnit,
- assetId,
- from,
- spender,
- })
+ const { allowanceCryptoBaseUnitResult, isApprovalRequired, isAllowanceResetRequired } =
+ useIsApprovalRequired({
+ amountCryptoBaseUnit,
+ assetId,
+ from,
+ spender,
+ })
const approveContractData = useMemo(() => {
if (!amountCryptoBaseUnit || !spender) return
@@ -72,6 +73,7 @@ export const useApprovalFees = ({
approveContractData,
evmFeesResult,
isApprovalRequired,
+ isAllowanceResetRequired,
}
}
diff --git a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts
index d67caf78ac2..5ae3428800e 100644
--- a/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts
+++ b/src/state/slices/tradeQuoteSlice/tradeQuoteSlice.ts
@@ -92,9 +92,6 @@ export const tradeQuoteSlice = createSlice({
const allowanceKey = isReset ? AllowanceKey.AllowanceReset : AllowanceKey.Approval
state.tradeExecution[action.payload.id][hopKey][allowanceKey].state =
TransactionExecutionState.Failed
- if (allowanceKey === AllowanceKey.AllowanceReset) {
- state.tradeExecution[action.payload.id][hopKey].state = HopExecutionState.AwaitingApproval
- }
},
// marks the approval tx as complete, but the allowance check needs to pass before proceeding to swap step
setApprovalTxComplete: (
@@ -106,9 +103,14 @@ export const tradeQuoteSlice = createSlice({
const allowanceKey = isReset ? AllowanceKey.AllowanceReset : AllowanceKey.Approval
state.tradeExecution[action.payload.id][hopKey][allowanceKey].state =
TransactionExecutionState.Complete
- if (allowanceKey === AllowanceKey.AllowanceReset) {
- state.tradeExecution[action.payload.id][hopKey].state = HopExecutionState.AwaitingApproval
- }
+ },
+ setApprovalResetComplete: (
+ state,
+ action: PayloadAction<{ hopIndex: number; id: TradeQuote['id'] }>,
+ ) => {
+ const { hopIndex } = action.payload
+ const key = hopIndex === 0 ? HopKey.FirstHop : HopKey.SecondHop
+ state.tradeExecution[action.payload.id][key].state = HopExecutionState.AwaitingApproval
},
// progresses the hop to the swap step after the allowance check has passed
setApprovalStepComplete: (