import { FiatAccountType, ObfuscatedFiatAccountData } from '@fiatconnect/fiatconnect-types' import { RouteProp } from '@react-navigation/native' import { NativeStackScreenProps } from '@react-navigation/native-stack' import BigNumber from 'bignumber.js' import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react' import { useAsync } from 'react-async-hook' import { useTranslation } from 'react-i18next' import { ActivityIndicator, BackHandler, SafeAreaView, StyleSheet, Text, View } from 'react-native' import AppAnalytics from 'src/analytics/AppAnalytics' import { FiatExchangeEvents } from 'src/analytics/Events' import BackButton from 'src/components/BackButton' import Button, { BtnSizes, BtnTypes } from 'src/components/Button' import CancelButton from 'src/components/CancelButton' import { FormatType } from 'src/components/CurrencyDisplay' import Dialog from 'src/components/Dialog' import LineItemRow from 'src/components/LineItemRow' import Touchable from 'src/components/Touchable' import { CryptoAmount, FiatAmount } from 'src/fiatExchanges/amount' import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' import { CICOFlow } from 'src/fiatExchanges/types' import { convertToFiatConnectFiatCurrency } from 'src/fiatconnect' import { fiatConnectQuotesErrorSelector, fiatConnectQuotesLoadingSelector, } from 'src/fiatconnect/selectors' import { createFiatConnectTransfer, refetchQuote } from 'src/fiatconnect/slice' import i18n from 'src/i18n' import { getDefaultLocalCurrencyCode, usdToLocalCurrencyRateSelector, } from 'src/localCurrency/selectors' import { emptyHeader } from 'src/navigator/Headers' import { navigate, navigateBack, navigateHome } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' import { useDispatch, useSelector } from 'src/redux/hooks' import colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import variables from 'src/styles/variables' import { useTokenInfo } from 'src/tokens/hooks' import { tokenAmountInSmallestUnit } from 'src/tokens/saga' import { TokenBalance, TokenBalanceWithAddress } from 'src/tokens/slice' import { getFeeCurrencyAndAmounts, prepareERC20TransferTransaction, } from 'src/viem/prepareTransactions' import { getSerializablePreparedTransaction } from 'src/viem/preparedTransactionSerialization' import { walletAddressSelector } from 'src/web3/selectors' type Props = NativeStackScreenProps const usePrepareBaseFiatConnectOutTransactions = ({ transferAmount, token, tokenId, }: { transferAmount: string token?: TokenBalance tokenId: string }) => { const walletAddress = useSelector(walletAddressSelector) return useAsync(async () => { if (!walletAddress) { throw new Error('Missing wallet address') } if (!token) { throw new Error(`Missing token info for token id ${tokenId}`) } if (!token.address) { throw new Error( `Fiatconnect only supports ERC-20 tokens. Token ${tokenId} is missing address` ) } // note that the receipient address on the prepared transaction is the // user's own address. this will be replaced by the fiatconnect provider // transfer address in the sagas if the user proceeds with the transaction. // the provider address is not known until making a POST request to the // provider endpoint, which we should only do if the user intends to proceed // with the transaction because it locks the quoteId and prevents it from // being used again. return prepareERC20TransferTransaction({ fromWalletAddress: walletAddress, toWalletAddress: walletAddress, amount: BigInt(tokenAmountInSmallestUnit(new BigNumber(transferAmount), token.decimals)), feeCurrencies: [token], // according to the FC spec, the fee currency is paid in the same token as the cash out token sendToken: token as TokenBalanceWithAddress, }) }, [token, tokenId, transferAmount]) } export default function FiatConnectReviewScreen({ route, navigation }: Props) { const { t } = useTranslation() const dispatch = useDispatch() const { flow, normalizedQuote, fiatAccount, shouldRefetchQuote } = route.params const fiatConnectQuotesLoading = useSelector(fiatConnectQuotesLoadingSelector) const fiatConnectQuotesError = useSelector(fiatConnectQuotesErrorSelector) const defaultLocaleCurrencyCode = useSelector(getDefaultLocalCurrencyCode) const [showingExpiredQuoteDialog, setShowingExpiredQuoteDialog] = useState( normalizedQuote.getGuaranteedUntil() < new Date() ) const showFeeDisclaimer = useMemo( () => normalizedQuote.getFiatType() !== convertToFiatConnectFiatCurrency(defaultLocaleCurrencyCode), [normalizedQuote, defaultLocaleCurrencyCode] ) const tokenId = normalizedQuote.getTokenId() const token = useTokenInfo(tokenId) const prepareTransactionsResult = usePrepareBaseFiatConnectOutTransactions({ transferAmount: normalizedQuote.getCryptoAmount(), tokenId: normalizedQuote.getTokenId(), token, }) const networkFee = getFeeCurrencyAndAmounts(prepareTransactionsResult.result) useEffect(() => { if (shouldRefetchQuote) { dispatch( refetchQuote({ flow, cryptoType: normalizedQuote.getCryptoType(), cryptoAmount: normalizedQuote.getCryptoAmount(), fiatAmount: normalizedQuote.getFiatAmount(), providerId: normalizedQuote.getProviderId(), fiatAccount, tokenId: normalizedQuote.getTokenId(), }) ) } }, [shouldRefetchQuote]) useLayoutEffect(() => { navigation.setOptions({ headerLeft: () => , }) }, [navigation]) useEffect(() => { function hardwareBackPress() { goBack() return true } const backHandler = BackHandler.addEventListener('hardwareBackPress', hardwareBackPress) return function cleanup() { backHandler.remove() } }, []) const goBack = () => { // Navigate Back unless the previous screen was FiatDetailsScreen const routes = navigation.getState().routes const previousScreen = routes[routes.length - 2] if (previousScreen?.name === Screens.FiatDetailsScreen) { navigate(Screens.SelectProvider, { flow: normalizedQuote.flow, tokenId: normalizedQuote.getTokenId(), amount: { fiat: parseFloat(normalizedQuote.getFiatAmount()), crypto: parseFloat(normalizedQuote.getCryptoAmount()), }, }) } else if (previousScreen?.name === Screens.FiatConnectRefetchQuote) { navigateHome() } else { navigateBack() } } const onPressBack = async () => { AppAnalytics.track(FiatExchangeEvents.cico_fc_review_back, { flow, provider: normalizedQuote.getProviderId(), }) goBack() } const onPressSupport = () => { AppAnalytics.track(FiatExchangeEvents.cico_fc_review_error_contact_support, { flow, provider: normalizedQuote.getProviderId(), }) navigate(Screens.SupportContact) } const onPressTryAgain = () => { AppAnalytics.track(FiatExchangeEvents.cico_fc_review_error_retry, { flow, provider: normalizedQuote.getProviderId(), }) dispatch( refetchQuote({ flow, cryptoType: normalizedQuote.getCryptoType(), cryptoAmount: normalizedQuote.getCryptoAmount(), fiatAmount: normalizedQuote.getFiatAmount(), providerId: normalizedQuote.getProviderId(), fiatAccount, tokenId: normalizedQuote.getTokenId(), }) ) } const getFeeDisclaimer = () => { switch (fiatAccount.fiatAccountType) { case FiatAccountType.BankAccount: return t('fiatConnectReviewScreen.bankFeeDisclaimer') case FiatAccountType.MobileMoney: return t('fiatConnectReviewScreen.mobileMoneyFeeDisclaimer') default: return '' } } if (fiatConnectQuotesError) { return ( {t('fiatConnectReviewScreen.failedRefetch.title')} {t('fiatConnectReviewScreen.failedRefetch.description')}