From 6ef9d490f536d2a179738615821a1463da16f153 Mon Sep 17 00:00:00 2001 From: Tomas Jakl Date: Thu, 2 Oct 2025 16:18:07 +0200 Subject: [PATCH 1/2] feat(suite-native): wire approval and revoke screen to trade data --- suite-native/intl/src/messages.ts | 1 + .../ExchangeApprovalLimitSheet.tsx | 66 +++++--- .../ExchangeApprovalLimitSheet.comp.test.tsx | 61 +++++--- .../ExchangePreview/ExchangePreviewView.tsx | 27 ++-- .../__tests__/useExchangeSelectQuote.test.ts | 146 +++++++++++++++++- .../hooks/exchange/useExchangeSelectQuote.ts | 36 ++++- .../screens/TradingExchangeApprovalScreen.tsx | 113 ++++++++++---- .../screens/TradingExchangePreviewScreen.tsx | 12 +- .../screens/TradingExchangeRevokeScreen.tsx | 86 +++++++++-- ...radingExchangeApprovalScreen.comp.test.tsx | 24 ++- ...TradingExchangePreviewScreen.comp.test.tsx | 6 +- .../TradingExchangeRevokeScreen.comp.test.tsx | 22 ++- .../__tests__/approvalStatusUtils.test.ts | 77 +++++++-- .../src/utils/general/approvalStatusUtils.ts | 30 +++- suite-native/navigation/package.json | 1 + suite-native/navigation/src/navigators.ts | 16 +- yarn.lock | 1 + 17 files changed, 588 insertions(+), 137 deletions(-) diff --git a/suite-native/intl/src/messages.ts b/suite-native/intl/src/messages.ts index 8652258c3376..6586ff927038 100644 --- a/suite-native/intl/src/messages.ts +++ b/suite-native/intl/src/messages.ts @@ -2422,6 +2422,7 @@ export const messages = { providerNamePlaceholder: 'Provider', providerReceiveAddressLabel: "{providerName}'s receive address", confirmationAlertTitle: 'Failed to confirm offer.', + approvalSuccessAlert: 'Spending approval confirmed.', }, tradingExchangeApprovalScreen: { title: 'Set {symbol} spending', diff --git a/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet.tsx b/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet.tsx index 6f1fff8d5184..f039f36baf45 100644 --- a/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet.tsx +++ b/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet.tsx @@ -1,34 +1,45 @@ import { memo, useEffect } from 'react'; import { useSelector } from 'react-redux'; +import { DexApprovalType, ExchangeTrade } from 'invity-api'; + import { TradingRootState, cryptoIdToNetworkSymbolAndContractAddress, selectTradingCoinSymbolByCryptoId, - selectTradingExchangeSelectedQuote, selectTradingProviderByNameAndTradeType, } from '@suite-common/trading'; +import { isNetworkSymbol } from '@suite-common/wallet-config'; +import { TokenSymbol } from '@suite-common/wallet-types'; import { BottomSheetModal, VStack, useBottomSheetModal } from '@suite-native/atoms'; +import { CryptoAmountFormatter, TokenAmountFormatter } from '@suite-native/formatters'; import { Translation } from '@suite-native/intl'; import { ExchangeApprovalLimitCard } from './ExchangeApprovalLimitCard'; -type ExchangeApprovalLimitSheetProps = { +export type ExchangeApprovalLimitSheetProps = { isVisible: boolean; onDismiss: () => void; + onApprovalTypeSelect: (type: DexApprovalType) => void; + selectedApprovalType: DexApprovalType; + quote: ExchangeTrade; }; export const ExchangeApprovalLimitSheet = memo( - ({ isVisible, onDismiss }: ExchangeApprovalLimitSheetProps) => { - const { bottomSheetRef, openModal } = useBottomSheetModal(); + ({ + isVisible, + onDismiss, + onApprovalTypeSelect, + selectedApprovalType, + quote, + }: ExchangeApprovalLimitSheetProps) => { + const { bottomSheetRef, openModal, closeModal } = useBottomSheetModal(); - useEffect(() => { - if (isVisible) { - openModal(); - } - }, [isVisible, openModal]); + useEffect( + () => (isVisible ? openModal() : closeModal()), + [isVisible, openModal, closeModal], + ); - const quote = useSelector(selectTradingExchangeSelectedQuote); const providerInfo = useSelector((state: TradingRootState) => selectTradingProviderByNameAndTradeType(state, quote?.exchange, 'exchange'), ); @@ -37,10 +48,6 @@ export const ExchangeApprovalLimitSheet = memo( selectTradingCoinSymbolByCryptoId(state, quote?.send), ); - if (!quote) { - return null; - } - const { symbol, contractAddress } = quote.send ? cryptoIdToNetworkSymbolAndContractAddress(quote.send) : {}; @@ -49,7 +56,22 @@ export const ExchangeApprovalLimitSheet = memo( return null; } - const limitAmount = `200.32 ${coinSymbol}`; //TODO + const formattedLimitAmount = + !!coinSymbol && + (isNetworkSymbol(coinSymbol) ? ( + + ) : ( + + )); return ( { - // TODO - }} + isChecked={selectedApprovalType === 'INFINITE'} + onChange={() => onApprovalTypeSelect('INFINITE')} /> { - // TODO - }} + isChecked={selectedApprovalType === 'MINIMAL'} + onChange={() => onApprovalTypeSelect('MINIMAL')} /> diff --git a/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/__tests__/ExchangeApprovalLimitSheet.comp.test.tsx b/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/__tests__/ExchangeApprovalLimitSheet.comp.test.tsx index cd38ee867595..c5223b24ad71 100644 --- a/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/__tests__/ExchangeApprovalLimitSheet.comp.test.tsx +++ b/suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/__tests__/ExchangeApprovalLimitSheet.comp.test.tsx @@ -5,6 +5,7 @@ import { getInitializedTradingState } from '../../../../__fixtures__/tradingStat import { ExchangeApprovalLimitSheet } from '../ExchangeApprovalLimitSheet'; const mockOnDismiss = jest.fn(); +const mockOnApprovalTypeSelect = jest.fn(); const testQuote = exchangeQuotes[0]; @@ -20,22 +21,20 @@ const getPreloadedState = (): PreloadedState => ({ }, }); -const getPreloadedStateWithoutQuote = (): PreloadedState => ({ - wallet: { - trading: { - ...getInitializedTradingState('exchange'), - exchange: { - ...getInitializedTradingState('exchange').exchange, - selectedQuote: undefined, - }, - }, - }, -}); - -const renderSheet = (isVisible = true, preloadedState = getPreloadedState()) => +const renderSheet = ( + isVisible = true, + quote = testQuote, + selectedApprovalType: 'INFINITE' | 'MINIMAL' = 'INFINITE', +) => renderWithStoreProviderAsync( - , - { preloadedState }, + , + { preloadedState: getPreloadedState() }, ); describe('ExchangeApprovalLimitSheet', () => { @@ -43,17 +42,11 @@ describe('ExchangeApprovalLimitSheet', () => { jest.clearAllMocks(); }); - it('should render nothing when quote is not available', async () => { - const { toJSON } = await renderSheet(true, getPreloadedStateWithoutQuote()); - - expect(toJSON()).toBeNull(); - }); - it('should render the sheet when visible', async () => { const { getByText } = await renderSheet(); expect(getByText('Unlimited')).toBeTruthy(); - expect(getByText('200.32 USDC')).toBeTruthy(); + expect(getByText('100 USDC')).toBeTruthy(); }); it('should render unlimited approval option with correct details', async () => { @@ -70,7 +63,7 @@ describe('ExchangeApprovalLimitSheet', () => { it('should render limited approval option with correct amount', async () => { const { getByText } = await renderSheet(); - expect(getByText('200.32 USDC')).toBeTruthy(); + expect(getByText('100 USDC')).toBeTruthy(); expect( getByText( "Approve only the amount needed for this swap. This helps reduce risk, but you'll need to approve again (and pay a fee) for future swaps.", @@ -94,4 +87,26 @@ describe('ExchangeApprovalLimitSheet', () => { ), ).toBeTruthy(); }); + + it('should display quote sendStringAmount in limited approval option', async () => { + const customQuote = { + ...testQuote, + sendStringAmount: '250', + }; + const { getByText } = await renderSheet(true, customQuote); + + expect(getByText('250 USDC')).toBeTruthy(); + }); + + it('should pass correct props when INFINITE is selected', async () => { + await renderSheet(true, testQuote, 'INFINITE'); + + expect(mockOnApprovalTypeSelect).toBeDefined(); + }); + + it('should pass correct props when MINIMAL is selected', async () => { + await renderSheet(true, testQuote, 'MINIMAL'); + + expect(mockOnApprovalTypeSelect).toBeDefined(); + }); }); diff --git a/suite-native/module-trading/src/components/exchange/ExchangePreview/ExchangePreviewView.tsx b/suite-native/module-trading/src/components/exchange/ExchangePreview/ExchangePreviewView.tsx index e1659dd502e1..0d6e4ae7338d 100644 --- a/suite-native/module-trading/src/components/exchange/ExchangePreview/ExchangePreviewView.tsx +++ b/suite-native/module-trading/src/components/exchange/ExchangePreview/ExchangePreviewView.tsx @@ -1,10 +1,10 @@ import { memo } from 'react'; -import { ScrollView } from 'react-native-gesture-handler'; import Animated from 'react-native-reanimated'; import { ExchangeTrade } from 'invity-api'; import { InlineAlertBox, VStack } from '@suite-native/atoms'; +import { Translation } from '@suite-native/intl'; import { ExchangeFeePickerCard } from './ExchangeFeePickerCard'; import { ExchangeFromAccountTradePreviewCard } from './ExchangeFromAccountTradePreviewCard'; @@ -14,15 +14,24 @@ import { useChangeStringsExtractor } from '../../../hooks/history/useChangeStrin export type ExchangePreviewViewProps = { quote: ExchangeTrade | undefined; txnErrorString: string | null; + isApproved?: boolean; }; -export const ExchangePreviewView = memo(({ quote, txnErrorString }: ExchangePreviewViewProps) => { - const { fromStringValue, toStringValue } = useChangeStringsExtractor(quote); - const isTxnError = !!txnErrorString; +export const ExchangePreviewView = memo( + ({ quote, txnErrorString, isApproved }: ExchangePreviewViewProps) => { + const { fromStringValue, toStringValue } = useChangeStringsExtractor(quote); + const isTxnError = !!txnErrorString; - return ( - + return ( + {!!isApproved && ( + + } + /> + )} {isTxnError && ( @@ -35,6 +44,6 @@ export const ExchangePreviewView = memo(({ quote, txnErrorString }: ExchangePrev - - ); -}); + ); + }, +); diff --git a/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts b/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts index 1a99d8e2a1c5..2b17c6406f72 100644 --- a/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts +++ b/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts @@ -13,6 +13,7 @@ import { exchangeQuotes } from '../../../__fixtures__/exchangeQuotes'; import { btcAsset } from '../../../__fixtures__/tradeableAssets'; import { getInitializedTradingStateWithQuotes } from '../../../__fixtures__/tradingState'; import { ExchangeFormType } from '../../../types/exchange'; +import * as approvalStatusUtils from '../../../utils/general/approvalStatusUtils'; import { useExchangeForm } from '../useExchangeForm'; import { useExchangeSelectQuote } from '../useExchangeSelectQuote'; @@ -275,7 +276,7 @@ describe('useExchangeSelectQuote', () => { nextStep(); }); - expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview'); + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {}); }); it('should call cancelConsent when quote provider changes', async () => { @@ -307,4 +308,147 @@ describe('useExchangeSelectQuote', () => { expect(result.current.isConsentRequested).toBe(false); }); }); + + describe('navigation based on approval status', () => { + beforeEach(async () => { + store = await getInitializedStore({ isLoading: false }); + + const { result } = await renderExchangeForm(); + exchangeForm = result.current; + }); + + it('should navigate to TradingExchangePreview when approval status is "approved"', async () => { + jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('approved'); + + act(() => { + exchangeForm.setValue('quote', exchangeQuotes[1]); + }); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const { result } = await renderUseExchangeSelectQuote(); + + act(() => { + result.current.selectQuote(); + }); + + const dispatchCall = dispatchSpy.mock.calls[0][0]; + const { nextStep } = (dispatchCall as any).payload; + + act(() => { + nextStep(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {}); + }); + + it('should navigate to TradingExchangePreview when approval status is "not_needed"', async () => { + jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('not_needed'); + + act(() => { + exchangeForm.setValue('quote', exchangeQuotes[1]); + }); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const { result } = await renderUseExchangeSelectQuote(); + + act(() => { + result.current.selectQuote(); + }); + + const dispatchCall = dispatchSpy.mock.calls[0][0]; + const { nextStep } = (dispatchCall as any).payload; + + act(() => { + nextStep(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {}); + }); + + it('should navigate to TradingExchangeApproval when approval status is "needs_approval"', async () => { + jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_approval'); + + act(() => { + exchangeForm.setValue('quote', exchangeQuotes[1]); + }); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const { result } = await renderUseExchangeSelectQuote(); + + act(() => { + result.current.selectQuote(); + }); + + const dispatchCall = dispatchSpy.mock.calls[0][0]; + const { nextStep } = (dispatchCall as any).payload; + + act(() => { + nextStep(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeApproval', { + quote: exchangeQuotes[1], + }); + }); + + it('should navigate to TradingExchangeApproval with shouldIncreaseLimit when approval status is "needs_increase" and token supports increasing allowance', async () => { + jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase'); + jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue( + true, + ); + + act(() => { + exchangeForm.setValue('quote', exchangeQuotes[1]); + }); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const { result } = await renderUseExchangeSelectQuote(); + + act(() => { + result.current.selectQuote(); + }); + + const dispatchCall = dispatchSpy.mock.calls[0][0]; + const { nextStep } = (dispatchCall as any).payload; + + act(() => { + nextStep(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeApproval', { + quote: exchangeQuotes[1], + shouldIncreaseLimit: true, + }); + }); + + it('should navigate to TradingExchangeRevoke when approval status is "needs_increase" and token does not support increasing allowance', async () => { + jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase'); + jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue( + false, + ); + + act(() => { + exchangeForm.setValue('quote', exchangeQuotes[1]); + }); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const { result } = await renderUseExchangeSelectQuote(); + + act(() => { + result.current.selectQuote(); + }); + + const dispatchCall = dispatchSpy.mock.calls[0][0]; + const { nextStep } = (dispatchCall as any).payload; + + act(() => { + nextStep(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeRevoke', { + quote: exchangeQuotes[1], + shouldIncreaseLimit: true, + }); + }); + }); }); diff --git a/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts b/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts index 51350b0623f3..6a6ed6c96865 100644 --- a/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts +++ b/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts @@ -5,6 +5,7 @@ import { useNavigation } from '@react-navigation/native'; import { exchangeThunks, + parseCryptoId, selectTradingExchangeIsLoading, selectTradingMaxSlippagePercentage, } from '@suite-common/trading'; @@ -23,6 +24,10 @@ import { selectExchangeSelectedSendAccount, } from '../../selectors/exchangeSelectors'; import { ExchangeFormType } from '../../types/exchange'; +import { + getApprovalStatus, + tokenSupportsIncreasingAllowance, +} from '../../utils/general/approvalStatusUtils'; import { isFullySelectedReceiveAccount } from '../../utils/general/receiveAccountUtils'; import { getSymbolFromTradeableAsset } from '../../utils/general/tradeableAssetUtils'; import { useConsent } from '../general/useConsent'; @@ -92,7 +97,36 @@ export const useExchangeSelectQuote = (form: ExchangeFormType) => { userConsent: waitForConsent, nextStep: () => { clearExchangeFormQuoteData(form); - navigation.navigate(TradingStackRoutes.TradingExchangePreview); + + const approvalStatus = getApprovalStatus(candidateQuote); + if (approvalStatus === 'approved' || approvalStatus === 'not_needed') { + return navigation.navigate(TradingStackRoutes.TradingExchangePreview, {}); + } + + const { contractAddress } = candidateQuote.send + ? parseCryptoId(candidateQuote.send) + : {}; + + const isIncreasingAllowanceSupported = + tokenSupportsIncreasingAllowance(contractAddress); + + if (approvalStatus === 'needs_increase' && isIncreasingAllowanceSupported) { + return navigation.navigate(TradingStackRoutes.TradingExchangeApproval, { + quote: candidateQuote, + shouldIncreaseLimit: true, + }); + } + + if (approvalStatus === 'needs_increase') { + return navigation.navigate(TradingStackRoutes.TradingExchangeRevoke, { + quote: candidateQuote, + shouldIncreaseLimit: true, + }); + } + + return navigation.navigate(TradingStackRoutes.TradingExchangeApproval, { + quote: candidateQuote, + }); }, onCancel: () => {}, }), diff --git a/suite-native/module-trading/src/screens/TradingExchangeApprovalScreen.tsx b/suite-native/module-trading/src/screens/TradingExchangeApprovalScreen.tsx index 2a8ad6866d81..276102116b21 100644 --- a/suite-native/module-trading/src/screens/TradingExchangeApprovalScreen.tsx +++ b/suite-native/module-trading/src/screens/TradingExchangeApprovalScreen.tsx @@ -1,12 +1,14 @@ -import { Pressable } from 'react-native'; +import { useState } from 'react'; +import { Pressable } from 'react-native-gesture-handler'; import { useSelector } from 'react-redux'; -import { invariant } from '@suite-common/suite-utils'; +import { useNavigation } from '@react-navigation/native'; +import { DexApprovalType } from 'invity-api'; + import { TradingRootState, cryptoIdToNetworkAndContractAddress, selectTradingCoinSymbolByCryptoId, - selectTradingExchangeSelectedQuote, selectTradingProviderByNameAndTradeType, } from '@suite-common/trading'; import { asBaseCurrencyAmount } from '@suite-common/wallet-utils'; @@ -14,22 +16,42 @@ import { Box, Button, Card, HStack, InlineAlertBox, Text, VStack } from '@suite- import { BaseCurrencyAmountFormatter } from '@suite-native/formatters'; import { CryptoIcon, Icon, NetworkIcon } from '@suite-native/icons'; import { Translation } from '@suite-native/intl'; -import { DynamicScreenHeader, Screen } from '@suite-native/navigation'; +import { + DynamicScreenHeader, + RootStackParamList, + Screen, + StackNavigationProps, + StackToStackCompositeScreenProps, + TradingStackParamList, + TradingStackRoutes, +} from '@suite-native/navigation'; import { BigNumber } from '@trezor/utils'; import { TradeInfoHeader } from '../components/TradeInfo/TradeInfoHeader'; import { TradeInfoRow } from '../components/TradeInfo/TradeInfoRow'; import { ExchangeApprovalLimitSheet } from '../components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet'; import { ProviderLogo } from '../components/general/ProviderLogo'; +import { useExchangeFlow } from '../hooks/exchange/useExchangeFlow'; import { useBottomSheetControls } from '../hooks/general/useBottomSheetControls'; import { selectExchangeSelectedSendAccount } from '../selectors/exchangeSelectors'; -export const TradingExchangeApprovalScreen = () => { - const quote = useSelector(selectTradingExchangeSelectedQuote); +type TradingExchangeApprovalScreenProps = StackToStackCompositeScreenProps< + TradingStackParamList, + TradingStackRoutes.TradingExchangeApproval, + RootStackParamList +>; - invariant(quote, 'quote must be defined'); +export const TradingExchangeApprovalScreen = ({ + route: { params }, +}: TradingExchangeApprovalScreenProps) => { + const { quote, shouldIncreaseLimit, isRevoked } = params; + const navigation = + useNavigation< + StackNavigationProps + >(); const account = useSelector(selectExchangeSelectedSendAccount); + const [selectedApprovalType, setSelectedApprovalType] = useState('INFINITE'); const { network, contractAddress } = quote.send ? cryptoIdToNetworkAndContractAddress(quote.send) @@ -45,8 +67,34 @@ export const TradingExchangeApprovalScreen = () => { selectTradingCoinSymbolByCryptoId(state, quote?.send), ); + const { confirmTrade } = useExchangeFlow(); + const fee = '4.76'; // TODO + const handleContinue = async () => { + const updatedQuote = { + ...quote, + approvalType: selectedApprovalType, + }; + + const success = await confirmTrade({ + receiveAddress: quote.receiveAddress ?? '', + trade: updatedQuote, + approvalFlow: true, + nextStep: () => {}, + }); + + if (success) { + // TODO + navigation.navigate(TradingStackRoutes.TradingExchangePreview, { isApproved: true }); + } + }; + + const handleApprovalTypeChange = (newType: DexApprovalType) => { + setSelectedApprovalType(newType); + hideSheet(); + }; + return ( { values={{ symbol: coinSymbol, companyName: providerInfo?.companyName }} /> } - closeActionType="close" + closeActionType="back" /> } > - - } - variant="success" - /> - - } - variant="info" - /> + {!!shouldIncreaseLimit && ( + + } + variant="warning" + /> + )} + + {!!isRevoked && ( + + } + /> + )} { /> )} - + {selectedApprovalType === 'INFINITE' ? ( + + ) : ( + `${quote.sendStringAmount} ${coinSymbol}` + )} @@ -177,15 +234,17 @@ export const TradingExchangeApprovalScreen = () => { - - + ); }; diff --git a/suite-native/module-trading/src/screens/TradingExchangePreviewScreen.tsx b/suite-native/module-trading/src/screens/TradingExchangePreviewScreen.tsx index 5040cb49cf0f..3931804573f1 100644 --- a/suite-native/module-trading/src/screens/TradingExchangePreviewScreen.tsx +++ b/suite-native/module-trading/src/screens/TradingExchangePreviewScreen.tsx @@ -35,7 +35,11 @@ export type TradingExchangePreviewScreenProps = StackProps< TradingStackRoutes.TradingExchangePreview >; -export const TradingExchangePreviewScreen = ({ navigation }: TradingExchangePreviewScreenProps) => { +export const TradingExchangePreviewScreen = ({ + navigation, + route: { params }, +}: TradingExchangePreviewScreenProps) => { + const { isApproved } = params; const { showAlert } = useAlert(); const dispatch = useDispatch(); const debounce = useDebounce(); @@ -146,7 +150,11 @@ export const TradingExchangePreviewScreen = ({ navigation }: TradingExchangePrev return ( }> - + { - const quote = useSelector(selectTradingExchangeSelectedQuote); +type TradingExchangeRevokeScreenProps = StackToStackCompositeScreenProps< + TradingStackParamList, + TradingStackRoutes.TradingExchangeRevoke, + RootStackParamList +>; + +type NavigationProps = StackToStackCompositeNavigationProps< + TradingStackParamList, + TradingStackRoutes.TradingExchangeRevoke, + RootStackParamList +>; - invariant(quote, 'quote must be defined'); +export const TradingExchangeRevokeScreen = ({ + route: { params }, +}: TradingExchangeRevokeScreenProps) => { + const { quote, shouldIncreaseLimit } = params; + + const navigation = useNavigation(); const account = useSelector(selectExchangeSelectedSendAccount); + const handleContinue = () => { + // TODO + if (shouldIncreaseLimit) { + return navigation.replace(TradingStackRoutes.TradingExchangeApproval, { + quote, + isRevoked: true, + }); + } + + return navigation.goBack(); + }; + const { network, contractAddress } = quote.send ? cryptoIdToNetworkAndContractAddress(quote.send) : {}; @@ -69,11 +104,16 @@ export const TradingExchangeRevokeScreen = () => { } > - } - variant="warning" - /> - + {!!shouldIncreaseLimit && ( + + + } + variant="warning" + /> + + )} } @@ -130,7 +170,23 @@ export const TradingExchangeRevokeScreen = () => { /> )} - + {!!coinSymbol && + (isNetworkSymbol(coinSymbol) ? ( + + ) : ( + + ))} @@ -185,11 +241,7 @@ export const TradingExchangeRevokeScreen = () => { - diff --git a/suite-native/module-trading/src/screens/__tests__/TradingExchangeApprovalScreen.comp.test.tsx b/suite-native/module-trading/src/screens/__tests__/TradingExchangeApprovalScreen.comp.test.tsx index 7ef57e0db40c..dc2f2a87d2ab 100644 --- a/suite-native/module-trading/src/screens/__tests__/TradingExchangeApprovalScreen.comp.test.tsx +++ b/suite-native/module-trading/src/screens/__tests__/TradingExchangeApprovalScreen.comp.test.tsx @@ -12,6 +12,13 @@ import { TradingExchangeApprovalScreen } from '../TradingExchangeApprovalScreen' const mockShowSheet = jest.fn(); const mockHideSheet = jest.fn(); +jest.mock('../../hooks/exchange/useExchangeFlow', () => ({ + useExchangeFlow: () => ({ + confirmTrade: jest.fn().mockResolvedValue(true), + fetchFeesAndCompose: jest.fn(), + }), +})); + jest.mock('@react-navigation/native', () => ({ ...jest.requireActual('@react-navigation/native'), useRoute: () => @@ -46,9 +53,20 @@ const preloadedState = { }; const renderScreen = () => - renderWithStoreProviderAsync(, { - preloadedState, - }); + renderWithStoreProviderAsync( + + } + navigation={{} as any} + />, + { + preloadedState, + }, + ); describe('TradingExchangeApprovalScreen', () => { beforeEach(() => { diff --git a/suite-native/module-trading/src/screens/__tests__/TradingExchangePreviewScreen.comp.test.tsx b/suite-native/module-trading/src/screens/__tests__/TradingExchangePreviewScreen.comp.test.tsx index 4da8340a23be..68b7d2083d64 100644 --- a/suite-native/module-trading/src/screens/__tests__/TradingExchangePreviewScreen.comp.test.tsx +++ b/suite-native/module-trading/src/screens/__tests__/TradingExchangePreviewScreen.comp.test.tsx @@ -22,7 +22,7 @@ jest.mock('@react-navigation/native', () => ({ ...jest.requireActual('@react-navigation/native'), useRoute: () => ({ - params: undefined, + params: {}, }) as RouteProp, })); @@ -56,7 +56,7 @@ describe('TradingExchangePreviewScreen', () => { let store: TestStore; const analyticsSpy = jest.spyOn(analytics, 'report'); - const renderTradingExchangePreviewScreen = () => + const renderTradingExchangePreviewScreen = (isApproved: boolean = false) => renderWithStoreProviderAsync( { popToTop: mockPopToTop, } as unknown as TradingExchangePreviewScreenProps['navigation'] } - route={{} as TradingExchangePreviewScreenProps['route']} + route={{ params: { isApproved } } as TradingExchangePreviewScreenProps['route']} />, { store }, ); diff --git a/suite-native/module-trading/src/screens/__tests__/TradingExchangeRevokeScreen.comp.test.tsx b/suite-native/module-trading/src/screens/__tests__/TradingExchangeRevokeScreen.comp.test.tsx index 93efb2304ab4..045d27d8f840 100644 --- a/suite-native/module-trading/src/screens/__tests__/TradingExchangeRevokeScreen.comp.test.tsx +++ b/suite-native/module-trading/src/screens/__tests__/TradingExchangeRevokeScreen.comp.test.tsx @@ -35,9 +35,20 @@ const preloadedState = { }; const renderScreen = () => - renderWithStoreProviderAsync(, { - preloadedState, - }); + renderWithStoreProviderAsync( + + } + navigation={{} as any} + />, + { + preloadedState, + }, + ); describe('TradingExchangeRevokeScreen', () => { it('should render the revoke screen with quote details', async () => { @@ -61,11 +72,12 @@ describe('TradingExchangeRevokeScreen', () => { }); it('should show current limit and new limit with crypto icon', async () => { - const { getByText } = await renderScreen(); + const { getByText, getAllByText } = await renderScreen(); expect(getByText('Current limit')).toBeOnTheScreen(); expect(getByText('New limit')).toBeOnTheScreen(); - expect(getByText('0 USDC')).toBeOnTheScreen(); + const usdcElements = getAllByText('0 USDC'); + expect(usdcElements).toHaveLength(2); }); it('should render continue button', async () => { diff --git a/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts b/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts index c98035eb816d..3e6510a6098d 100644 --- a/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts +++ b/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts @@ -1,4 +1,4 @@ -import { getApprovalStatus } from '../approvalStatusUtils'; +import { getApprovalStatus, tokenSupportsIncreasingAllowance } from '../approvalStatusUtils'; describe('getApprovalStatus', () => { it('should return null when no quote is provided', () => { @@ -6,16 +6,48 @@ describe('getApprovalStatus', () => { expect(result).toBe(null); }); - it('should return "approved" when quote has preapprovedStringAmount', () => { + it('should return "approved" when quote has preapprovedStringAmount and is not APPROVAL_REQ', () => { const quote = { orderId: 'test-order', preapprovedStringAmount: '0.001', - isDex: false, + isDex: true, + status: 'CONFIRM' as const, + }; + const result = getApprovalStatus(quote); + expect(result).toBe('approved'); + }); + + it('should return "approved" when quote has preapprovedStringAmount !== "0" without status', () => { + const quote = { + orderId: 'test-order', + preapprovedStringAmount: '0.001', + isDex: true, }; const result = getApprovalStatus(quote); expect(result).toBe('approved'); }); + it('should return "needs_increase" when quote has preapprovedStringAmount !== "0" and status is APPROVAL_REQ', () => { + const quote = { + orderId: 'test-order', + preapprovedStringAmount: '0.001', + isDex: true, + status: 'APPROVAL_REQ' as const, + }; + const result = getApprovalStatus(quote); + expect(result).toBe('needs_increase'); + }); + + it('should return "needs_approval" when preapprovedStringAmount is "0" and isDex is true', () => { + const quote = { + orderId: 'test-order', + preapprovedStringAmount: '0', + isDex: true, + }; + const result = getApprovalStatus(quote); + expect(result).toBe('needs_approval'); + }); + it('should return "needs_approval" when quote is DEX', () => { const quote = { orderId: 'test-order', @@ -35,14 +67,37 @@ describe('getApprovalStatus', () => { const result = getApprovalStatus(quote); expect(result).toBe('not_needed'); }); +}); - it('should prioritize preapprovedStringAmount over isDex', () => { - const quote = { - orderId: 'test-order', - preapprovedStringAmount: '0.001', - isDex: true, - }; - const result = getApprovalStatus(quote); - expect(result).toBe('approved'); +describe('tokenSupportsIncreasingAllowance', () => { + it('should return false for Ethereum USDT contract address (uppercase)', () => { + const result = tokenSupportsIncreasingAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + ); + expect(result).toBe(false); + }); + + it('should return false for Ethereum USDT contract address (lowercase)', () => { + const result = tokenSupportsIncreasingAllowance( + '0xdac17f958d2ee523a2206206994597c13d831ec7', + ); + expect(result).toBe(false); + }); + + it('should return true for other contract addresses', () => { + const result = tokenSupportsIncreasingAllowance( + '0x1234567890123456789012345678901234567890', + ); + expect(result).toBe(true); + }); + + it('should return false for undefined contract address', () => { + const result = tokenSupportsIncreasingAllowance(undefined); + expect(result).toBe(false); + }); + + it('should return false for empty string', () => { + const result = tokenSupportsIncreasingAllowance(''); + expect(result).toBe(false); }); }); diff --git a/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts b/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts index 0279371c283f..9e89655339e1 100644 --- a/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts +++ b/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts @@ -1,22 +1,36 @@ import type { ExchangeTrade } from 'invity-api'; -import { BigNumber } from '@trezor/utils'; - -export type ApprovalStatus = 'approved' | 'needs_approval' | 'not_needed' | null; +export type ApprovalStatus = 'approved' | 'needs_approval' | 'needs_increase' | 'not_needed' | null; export const getApprovalStatus = (candidateQuote?: ExchangeTrade): ApprovalStatus => { if (!candidateQuote) { return null; } - const preapproved = new BigNumber(candidateQuote.preapprovedStringAmount ?? '0'); - if (preapproved.gt(0)) { + if (!candidateQuote.isDex) { + return 'not_needed'; + } + + const isApprovalTxPreApproved = + candidateQuote.preapprovedStringAmount && candidateQuote.preapprovedStringAmount !== '0'; + + if (isApprovalTxPreApproved && candidateQuote.status === 'APPROVAL_REQ') { + return 'needs_increase'; + } + + if (isApprovalTxPreApproved) { return 'approved'; } - if (candidateQuote.isDex) { - return 'needs_approval'; + return 'needs_approval'; +}; + +export const tokenSupportsIncreasingAllowance = (contractAddress?: string): boolean => { + const ethereumUsdtContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + + if (!contractAddress) { + return false; } - return 'not_needed'; + return contractAddress.trim().toLowerCase() !== ethereumUsdtContractAddress.toLowerCase(); }; diff --git a/suite-native/navigation/package.json b/suite-native/navigation/package.json index 082b53382877..098f3184d235 100644 --- a/suite-native/navigation/package.json +++ b/suite-native/navigation/package.json @@ -32,6 +32,7 @@ "@trezor/styles": "workspace:*", "@trezor/theme": "workspace:*", "@trezor/utils": "workspace:*", + "@types/invity-api": "^1.1.11", "expo-linear-gradient": "~14.1.5", "expo-system-ui": "5.0.10", "react": "19.0.0", diff --git a/suite-native/navigation/src/navigators.ts b/suite-native/navigation/src/navigators.ts index fc60b369cd40..4ac8649d6735 100644 --- a/suite-native/navigation/src/navigators.ts +++ b/suite-native/navigation/src/navigators.ts @@ -1,4 +1,5 @@ import { NavigatorScreenParams } from '@react-navigation/native'; +import type { ExchangeTrade } from 'invity-api'; import { RequireAllOrNone } from 'type-fest'; import { BackupType } from '@suite-common/suite-types'; @@ -344,9 +345,18 @@ export type TradingStackParamList = { tradingType: Exclude; }; [TradingStackRoutes.TradingHistory]: undefined; - [TradingStackRoutes.TradingExchangePreview]: undefined; - [TradingStackRoutes.TradingExchangeApproval]: undefined; - [TradingStackRoutes.TradingExchangeRevoke]: undefined; + [TradingStackRoutes.TradingExchangePreview]: { + isApproved?: boolean; + }; + [TradingStackRoutes.TradingExchangeApproval]: { + quote: ExchangeTrade; + shouldIncreaseLimit?: boolean; + isRevoked?: boolean; + }; + [TradingStackRoutes.TradingExchangeRevoke]: { + quote: ExchangeTrade; + shouldIncreaseLimit?: boolean; + }; [TradingStackRoutes.TradingFees]: { accountKey: AccountKey; tradingType: TradingType; diff --git a/yarn.lock b/yarn.lock index f9ffaace604b..f53dd126cae3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12355,6 +12355,7 @@ __metadata: "@trezor/styles": "workspace:*" "@trezor/theme": "workspace:*" "@trezor/utils": "workspace:*" + "@types/invity-api": "npm:^1.1.11" expo-linear-gradient: "npm:~14.1.5" expo-system-ui: "npm:5.0.10" react: "npm:19.0.0" From 8d8ac515fc1b788ca1747338958dba457049f1ef Mon Sep 17 00:00:00 2001 From: Tomas Jakl Date: Wed, 8 Oct 2025 08:49:11 +0200 Subject: [PATCH 2/2] refactor(suite-common): move tokenSupportsIncreasingAllowance to suite-common --- .../UserContextModal/RevokeModal.tsx | 8 +++-- .../src/utils/wallet/trading/tradingUtils.ts | 9 ----- .../TradingForm/TradingFormApproval.tsx | 7 ++-- .../exchange/__tests__/exchangeUtils.test.ts | 34 ++++++++++++++++++ .../src/utils/exchange/exchangeUtils.ts | 11 ++++++ .../__tests__/useExchangeSelectQuote.test.ts | 12 +++---- .../hooks/exchange/useExchangeSelectQuote.ts | 6 ++-- .../__tests__/approvalStatusUtils.test.ts | 35 +------------------ .../src/utils/general/approvalStatusUtils.ts | 10 ------ 9 files changed, 65 insertions(+), 67 deletions(-) create mode 100644 suite-common/trading/src/utils/exchange/__tests__/exchangeUtils.test.ts diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/RevokeModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/RevokeModal.tsx index 11468a22b91a..8e42406b115c 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/RevokeModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/RevokeModal.tsx @@ -2,7 +2,12 @@ import { useState } from 'react'; import styled from 'styled-components'; -import { TradingExchangeType, invityAPI, useTradingInfo } from '@suite-common/trading'; +import { + TradingExchangeType, + invityAPI, + tokenSupportsIncreasingAllowance, + useTradingInfo, +} from '@suite-common/trading'; import { getDisplaySymbol } from '@suite-common/wallet-config'; import { Badge, @@ -28,7 +33,6 @@ import { useTradingFormContext } from 'src/hooks/wallet/trading/form/useTradingC import { useTradingExchangeCryptoAndProviderInfo } from 'src/hooks/wallet/trading/form/useTradingExchangeCryptoAndProviderInfo'; import { selectIsDebugModeActive } from 'src/selectors/suite/suiteSelectors'; import { getProvidersInfoProps } from 'src/utils/wallet/trading/tradingTypingUtils'; -import { tokenSupportsIncreasingAllowance } from 'src/utils/wallet/trading/tradingUtils'; import { TradingCoinLogo } from 'src/views/wallet/trading/common/TradingCoinLogo'; const BreakableValue = styled.span` diff --git a/packages/suite/src/utils/wallet/trading/tradingUtils.ts b/packages/suite/src/utils/wallet/trading/tradingUtils.ts index 179a0405af6c..cc3eec8155c0 100644 --- a/packages/suite/src/utils/wallet/trading/tradingUtils.ts +++ b/packages/suite/src/utils/wallet/trading/tradingUtils.ts @@ -390,12 +390,3 @@ export const getTradeTypeByRoute = ( return 'exchange'; } }; - -export const tokenSupportsIncreasingAllowance = (contractAddress?: string) => { - const ethereumUsdtContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; - - return ( - contractAddress && - contractAddress.trim().toLowerCase() !== ethereumUsdtContractAddress.toLowerCase() - ); -}; diff --git a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormApproval.tsx b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormApproval.tsx index fa86961ee034..fb9c4362faba 100644 --- a/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormApproval.tsx +++ b/packages/suite/src/views/wallet/trading/common/TradingForm/TradingFormApproval.tsx @@ -3,7 +3,11 @@ import { usePrevious } from 'react-use'; import styled, { DefaultTheme, keyframes } from 'styled-components'; -import { TradingExchangeType, useTradingInfo } from '@suite-common/trading'; +import { + TradingExchangeType, + tokenSupportsIncreasingAllowance, + useTradingInfo, +} from '@suite-common/trading'; import { selectHasRunningDiscovery } from '@suite-common/wallet-core'; import { Banner, Button, Column, Icon, Paragraph, Row } from '@trezor/components'; import { EventType, analytics } from '@trezor/suite-analytics'; @@ -16,7 +20,6 @@ import { useTradingFormContext } from 'src/hooks/wallet/trading/form/useTradingC import { useTradingExchangeCryptoAndProviderInfo } from 'src/hooks/wallet/trading/form/useTradingExchangeCryptoAndProviderInfo'; import { useTradingExchangeWatchApproval } from 'src/hooks/wallet/trading/form/useTradingExchangeWatchApproval'; import { TradingExchangeApprovalType } from 'src/types/trading/tradingForm'; -import { tokenSupportsIncreasingAllowance } from 'src/utils/wallet/trading/tradingUtils'; const TextButton = styled.div<{ $disabled: boolean }>` color: ${({ theme, $disabled }) => diff --git a/suite-common/trading/src/utils/exchange/__tests__/exchangeUtils.test.ts b/suite-common/trading/src/utils/exchange/__tests__/exchangeUtils.test.ts new file mode 100644 index 000000000000..c01db65b785b --- /dev/null +++ b/suite-common/trading/src/utils/exchange/__tests__/exchangeUtils.test.ts @@ -0,0 +1,34 @@ +import { tokenSupportsIncreasingAllowance } from '../exchangeUtils'; + +describe('tokenSupportsIncreasingAllowance', () => { + it('should return false for Ethereum USDT contract address (uppercase)', () => { + const result = tokenSupportsIncreasingAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + ); + expect(result).toBe(false); + }); + + it('should return false for Ethereum USDT contract address (lowercase)', () => { + const result = tokenSupportsIncreasingAllowance( + '0xdac17f958d2ee523a2206206994597c13d831ec7', + ); + expect(result).toBe(false); + }); + + it('should return true for other contract addresses', () => { + const result = tokenSupportsIncreasingAllowance( + '0x1234567890123456789012345678901234567890', + ); + expect(result).toBe(true); + }); + + it('should return false for undefined contract address', () => { + const result = tokenSupportsIncreasingAllowance(undefined); + expect(result).toBe(false); + }); + + it('should return false for empty string', () => { + const result = tokenSupportsIncreasingAllowance(''); + expect(result).toBe(false); + }); +}); diff --git a/suite-common/trading/src/utils/exchange/exchangeUtils.ts b/suite-common/trading/src/utils/exchange/exchangeUtils.ts index 867520e4144a..ab119a60fae6 100644 --- a/suite-common/trading/src/utils/exchange/exchangeUtils.ts +++ b/suite-common/trading/src/utils/exchange/exchangeUtils.ts @@ -116,6 +116,16 @@ export const getStatusMessage = (status: ExchangeTradeStatus) => { } }; +export const tokenSupportsIncreasingAllowance = (contractAddress?: string): boolean => { + const ethereumUsdtContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + + if (!contractAddress) { + return false; + } + + return contractAddress.trim().toLowerCase() !== ethereumUsdtContractAddress.toLowerCase(); +}; + export const exchangeUtils = { getAmountLimits, isQuoteError, @@ -124,4 +134,5 @@ export const exchangeUtils = { getCexQuotesByRateType, getSuccessQuotesOrdered, getStatusMessage, + tokenSupportsIncreasingAllowance, }; diff --git a/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts b/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts index 2b17c6406f72..88fa31d18c27 100644 --- a/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts +++ b/suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts @@ -28,6 +28,8 @@ jest.mock('@trezor/react-utils', () => ({ useTimer: () => mockTimerReturn, })); +const mockTokenSupportsIncreasingAllowance = jest.fn(); + jest.mock('@suite-common/trading', () => ({ ...jest.requireActual('@suite-common/trading'), exchangeThunks: { @@ -36,6 +38,8 @@ jest.mock('@suite-common/trading', () => ({ payload, }), }, + tokenSupportsIncreasingAllowance: (contractAddress?: string) => + mockTokenSupportsIncreasingAllowance(contractAddress), })); const mockNavigation = { @@ -393,9 +397,7 @@ describe('useExchangeSelectQuote', () => { it('should navigate to TradingExchangeApproval with shouldIncreaseLimit when approval status is "needs_increase" and token supports increasing allowance', async () => { jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase'); - jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue( - true, - ); + mockTokenSupportsIncreasingAllowance.mockReturnValue(true); act(() => { exchangeForm.setValue('quote', exchangeQuotes[1]); @@ -423,9 +425,7 @@ describe('useExchangeSelectQuote', () => { it('should navigate to TradingExchangeRevoke when approval status is "needs_increase" and token does not support increasing allowance', async () => { jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase'); - jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue( - false, - ); + mockTokenSupportsIncreasingAllowance.mockReturnValue(false); act(() => { exchangeForm.setValue('quote', exchangeQuotes[1]); diff --git a/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts b/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts index 6a6ed6c96865..38fb490a08db 100644 --- a/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts +++ b/suite-native/module-trading/src/hooks/exchange/useExchangeSelectQuote.ts @@ -8,6 +8,7 @@ import { parseCryptoId, selectTradingExchangeIsLoading, selectTradingMaxSlippagePercentage, + tokenSupportsIncreasingAllowance, } from '@suite-common/trading'; import { RootStackParamList, @@ -24,10 +25,7 @@ import { selectExchangeSelectedSendAccount, } from '../../selectors/exchangeSelectors'; import { ExchangeFormType } from '../../types/exchange'; -import { - getApprovalStatus, - tokenSupportsIncreasingAllowance, -} from '../../utils/general/approvalStatusUtils'; +import { getApprovalStatus } from '../../utils/general/approvalStatusUtils'; import { isFullySelectedReceiveAccount } from '../../utils/general/receiveAccountUtils'; import { getSymbolFromTradeableAsset } from '../../utils/general/tradeableAssetUtils'; import { useConsent } from '../general/useConsent'; diff --git a/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts b/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts index 3e6510a6098d..f4fba70de12f 100644 --- a/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts +++ b/suite-native/module-trading/src/utils/general/__tests__/approvalStatusUtils.test.ts @@ -1,4 +1,4 @@ -import { getApprovalStatus, tokenSupportsIncreasingAllowance } from '../approvalStatusUtils'; +import { getApprovalStatus } from '../approvalStatusUtils'; describe('getApprovalStatus', () => { it('should return null when no quote is provided', () => { @@ -68,36 +68,3 @@ describe('getApprovalStatus', () => { expect(result).toBe('not_needed'); }); }); - -describe('tokenSupportsIncreasingAllowance', () => { - it('should return false for Ethereum USDT contract address (uppercase)', () => { - const result = tokenSupportsIncreasingAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - ); - expect(result).toBe(false); - }); - - it('should return false for Ethereum USDT contract address (lowercase)', () => { - const result = tokenSupportsIncreasingAllowance( - '0xdac17f958d2ee523a2206206994597c13d831ec7', - ); - expect(result).toBe(false); - }); - - it('should return true for other contract addresses', () => { - const result = tokenSupportsIncreasingAllowance( - '0x1234567890123456789012345678901234567890', - ); - expect(result).toBe(true); - }); - - it('should return false for undefined contract address', () => { - const result = tokenSupportsIncreasingAllowance(undefined); - expect(result).toBe(false); - }); - - it('should return false for empty string', () => { - const result = tokenSupportsIncreasingAllowance(''); - expect(result).toBe(false); - }); -}); diff --git a/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts b/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts index 9e89655339e1..d6aa84d17da4 100644 --- a/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts +++ b/suite-native/module-trading/src/utils/general/approvalStatusUtils.ts @@ -24,13 +24,3 @@ export const getApprovalStatus = (candidateQuote?: ExchangeTrade): ApprovalStatu return 'needs_approval'; }; - -export const tokenSupportsIncreasingAllowance = (contractAddress?: string): boolean => { - const ethereumUsdtContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; - - if (!contractAddress) { - return false; - } - - return contractAddress.trim().toLowerCase() !== ethereumUsdtContractAddress.toLowerCase(); -};