From 5f663a84fdabfb68bb2712d09570da304aa1c3e8 Mon Sep 17 00:00:00 2001 From: DMY <147dmy@gmail.com> Date: Fri, 2 Aug 2024 15:55:36 +0800 Subject: [PATCH] fix --- .../src/assets/icons/swap/max-button.svg | 12 +- .../SelectSortedChain/ChainItem.tsx | 82 ++-- .../Bridge/components/BridgeContent.tsx | 45 +-- .../Bridge/components/BridgeHistory.tsx | 16 +- .../Bridge/components/BridgeQuoteItem.tsx | 18 +- .../Bridge/components/BridgeQuotes.tsx | 10 +- .../Bridge/components/BridgeReceiveDetail.tsx | 13 +- .../Bridge/components/BridgeTokenPair.tsx | 14 +- .../src/screens/Bridge/components/loading.tsx | 9 +- .../mobile/src/screens/Bridge/hooks/token.tsx | 363 ++++++++++-------- 10 files changed, 312 insertions(+), 270 deletions(-) diff --git a/apps/mobile/src/assets/icons/swap/max-button.svg b/apps/mobile/src/assets/icons/swap/max-button.svg index 8914329a4..12f886656 100644 --- a/apps/mobile/src/assets/icons/swap/max-button.svg +++ b/apps/mobile/src/assets/icons/swap/max-button.svg @@ -1,12 +1,12 @@ - - + + - + \ No newline at end of file diff --git a/apps/mobile/src/components/SelectSortedChain/ChainItem.tsx b/apps/mobile/src/components/SelectSortedChain/ChainItem.tsx index 26ef5e839..e57d83013 100644 --- a/apps/mobile/src/components/SelectSortedChain/ChainItem.tsx +++ b/apps/mobile/src/components/SelectSortedChain/ChainItem.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { Image, Text, View } from 'react-native'; import { CHAINS_ENUM, Chain } from '@/constant/chains'; @@ -14,6 +14,7 @@ import { RcWalletCC } from '@/assets/icons/common'; import { formatUsdValue } from '@/utils/number'; import { toast } from '../Toast'; import { TestnetChainLogo } from '../Chain/TestnetChainLogo'; +import { Tip } from '../Tip'; export default function ChainItem({ data, @@ -48,44 +49,51 @@ export default function ChainItem({ return disabledTips; }, [data, disabledTips]); + const [tipsVisible, setTipsVisible] = useState(false); + return ( - { - if (disabled) { - finalDisabledTips && toast.info(finalDisabledTips); - return; - } - onPress?.(data?.enum); - }}> - {data.isTestnet ? ( - - ) : ( - - )} - - - {data?.name} - {!!chainBalanceItem?.usd_value && ( - - - - {formatUsdValue(chainBalanceItem?.usd_value || 0)} - - - )} - - - {value && value === data?.enum ? : null} + setTipsVisible(false)}> + { + if (disabled) { + finalDisabledTips && setTipsVisible(true); // toast.info(finalDisabledTips); + return; + } + onPress?.(data?.enum); + }}> + {data.isTestnet ? ( + + ) : ( + + )} + + + {data?.name} + {!!chainBalanceItem?.usd_value && ( + + + + {formatUsdValue(chainBalanceItem?.usd_value || 0)} + + + )} + + + {value && value === data?.enum ? : null} + - - + + ); } diff --git a/apps/mobile/src/screens/Bridge/components/BridgeContent.tsx b/apps/mobile/src/screens/Bridge/components/BridgeContent.tsx index 5b0dd7c53..bd2347915 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeContent.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeContent.tsx @@ -16,8 +16,6 @@ import { useQuoteVisible, useSetQuoteVisible, useSetRefreshId, - useSetSettingVisible, - useSettingVisible, useTokenPair, } from '../hooks'; import { useCurrentAccount } from '@/hooks/account'; @@ -100,7 +98,7 @@ const getStyles = createGetStyles(colors => ({ flexDirection: 'row', height: 52, borderRadius: 4, - borderWidth: StyleSheet.hairlineWidth, + borderWidth: 1, borderColor: colors['neutral-line'], paddingHorizontal: 12, alignItems: 'center', @@ -188,15 +186,15 @@ export const BridgeContent = () => { handleAmountChange, handleBalance, - inputAmount, - debouncePayAmount, + payAmount, inSufficient, openQuotesList, quoteLoading, quoteList, - notAvailableQuote, + noBestQuote, + bestQuoteId, selectedBridgeQuote, @@ -243,7 +241,7 @@ export const BridgeContent = () => { from_token_id: payToken.id, user_addr: currentAccount?.address, from_chain_id: payToken.chain, - from_token_raw_amount: new BigNumber(debouncePayAmount) + from_token_raw_amount: new BigNumber(payAmount) .times(10 ** payToken.decimals) .toFixed(0, 1) .toString(), @@ -260,14 +258,14 @@ export const BridgeContent = () => { toTokenId: receiveToken.id, toChainId: receiveToken.chain, status: tx ? 'success' : 'fail', - payAmount: debouncePayAmount, + payAmount: payAmount, }); bridgeToken( { to: tx.to, value: tx.value, data: tx.data, - payTokenRawAmount: new BigNumber(debouncePayAmount) + payTokenRawAmount: new BigNumber(payAmount) .times(10 ** payToken.decimals) .toFixed(0, 1) .toString(), @@ -281,7 +279,7 @@ export const BridgeContent = () => { bridge_id: selectedBridgeQuote.bridge_id, from_chain_id: payToken.chain, from_token_id: payToken.id, - from_token_amount: debouncePayAmount, + from_token_amount: payAmount, to_chain_id: receiveToken.chain, to_token_id: receiveToken.id, to_token_amount: selectedBridgeQuote.to_token_amount, @@ -307,7 +305,7 @@ export const BridgeContent = () => { toTokenId: receiveToken.id, toChainId: receiveToken.chain, status: 'fail', - payAmount: debouncePayAmount, + payAmount: payAmount, }); console.error(error); } finally { @@ -377,14 +375,14 @@ export const BridgeContent = () => { - + { placeholderTextColor={colors['neutral-foot']} /> - {inputAmount + {payAmount ? `≈ ${formatUsdValue( - new BigNumber(inputAmount) + new BigNumber(payAmount) .times(payToken?.price || 0) .toString(10), )}` @@ -405,24 +403,23 @@ export const BridgeContent = () => { {quoteLoading && - Number(debouncePayAmount) > 0 && + Number(payAmount) > 0 && !inSufficient && !selectedBridgeQuote?.manualClick && } {payToken && !inSufficient && receiveToken && - Number(debouncePayAmount) > 0 && - (!quoteLoading || - (selectedBridgeQuote && selectedBridgeQuote?.manualClick)) && ( + Number(payAmount) > 0 && + (!quoteLoading || selectedBridgeQuote?.manualClick) && ( )} @@ -467,7 +464,7 @@ export const BridgeContent = () => { disabled={ !payToken || !receiveToken || - !debouncePayAmount || + Number(payAmount) > 0 || inSufficient || !selectedBridgeQuote } @@ -482,7 +479,7 @@ export const BridgeContent = () => { onConfirm={gotoBridge} /> - {payToken && receiveToken && Number(debouncePayAmount) > 0 && chain ? ( + {payToken && receiveToken && Number(payAmount) > 0 && chain ? ( { userAddress={currentAccount?.address || ''} chain={chain} payToken={payToken} - payAmount={debouncePayAmount} + payAmount={payAmount} receiveToken={receiveToken} inSufficient={inSufficient} setSelectedBridgeQuote={setSelectedBridgeQuote} diff --git a/apps/mobile/src/screens/Bridge/components/BridgeHistory.tsx b/apps/mobile/src/screens/Bridge/components/BridgeHistory.tsx index c58ee5758..53c890804 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeHistory.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeHistory.tsx @@ -197,14 +197,14 @@ const Transaction = forwardRef( - + {t('page.bridge.detail-tx')}:{' '} {txId ? ellipsis(txId) : ''} - + {!loading ? t('page.bridge.gas-fee', { gasUsed }) : t('page.bridge.gas-x-price', { @@ -440,6 +440,7 @@ const getStyles = createGetStyles(colors => ({ paddingTop: 10, borderTopWidth: 0.5, borderColor: colors['neutral-line'], + gap: 4, }, transactionDetail: { fontSize: 13, @@ -449,9 +450,12 @@ const getStyles = createGetStyles(colors => ({ textDecorationLine: 'underline', }, gasFee: { + textAlign: 'right', marginLeft: 'auto', fontSize: 13, color: colors['neutral-foot'], + flex: 1, + flexWrap: 'wrap', }, emptyContainer: { width: '100%', @@ -466,7 +470,7 @@ const getStyles = createGetStyles(colors => ({ emptyText: { textAlign: 'center', fontSize: 14, - color: 'rgba(62, 73, 94, 1)', // Update as needed + color: colors['neutral-foot'], }, historyList: { maxHeight: 434, @@ -493,7 +497,7 @@ const getStyles = createGetStyles(colors => ({ borderRadius: 6, }, emptyView: { - marginTop: 100, + marginTop: '50%', flex: 1, alignItems: 'center', justifyContent: 'center', @@ -503,8 +507,8 @@ const getStyles = createGetStyles(colors => ({ fontSize: 20, fontWeight: '500', textAlign: 'center', - paddingTop: 20, - paddingBottom: 16, + paddingTop: 16, + paddingBottom: 4, backgroundColor: colors['neutral-bg-2'], color: colors['neutral-title-1'], }, diff --git a/apps/mobile/src/screens/Bridge/components/BridgeQuoteItem.tsx b/apps/mobile/src/screens/Bridge/components/BridgeQuoteItem.tsx index 73c17ef6e..bbb936bab 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeQuoteItem.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeQuoteItem.tsx @@ -152,7 +152,7 @@ export const BridgeQuoteItem: React.FC = props => { true}> - + {t('page.bridge.estimated-value', { value: formatUsdValue( new BigNumber(props.to_token_amount) @@ -180,7 +180,10 @@ export const BridgeQuoteItem: React.FC = props => { styles.badge, props.isBestQuote ? styles.bestBadge : styles.diffBadge, ]}> - + {props.isBestQuote ? t('page.bridge.best') : diffPercent} @@ -229,8 +232,8 @@ const getStyles = createGetStyles(colors => ({ flexDirection: 'row', alignItems: 'center', gap: 8, - flex: 1, justifyContent: 'flex-end', + flexShrink: 1, }, aggregatorName: { fontSize: 16, @@ -273,6 +276,7 @@ const getStyles = createGetStyles(colors => ({ flexDirection: 'row', alignItems: 'center', gap: 6, + flex: 1, }, estimatedValueText: { fontSize: 13, @@ -298,9 +302,15 @@ const getStyles = createGetStyles(colors => ({ diffBadge: { backgroundColor: colors['red-light'], }, - badgeText: { + + bestQuoteText: { fontSize: 12, fontWeight: '500', color: colors['green-default'], }, + otherQuoteText: { + fontSize: 12, + fontWeight: '500', + color: colors['red-default'], + }, })); diff --git a/apps/mobile/src/screens/Bridge/components/BridgeQuotes.tsx b/apps/mobile/src/screens/Bridge/components/BridgeQuotes.tsx index 6b60edf4d..0ed4c5b0d 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeQuotes.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeQuotes.tsx @@ -24,7 +24,7 @@ const getStyles = createGetStyles(colors => ({ headerContainer: { flexDirection: 'row', alignItems: 'center', - paddingVertical: 20, + paddingTop: 20, paddingLeft: 20, alignSelf: 'stretch', gap: 3, @@ -39,6 +39,7 @@ const getStyles = createGetStyles(colors => ({ fontSize: 12, fontWeight: '500', color: colors['neutral-body'], + marginLeft: 4, }, radioContainer: { @@ -63,6 +64,7 @@ const getStyles = createGetStyles(colors => ({ gap: 12, marginBottom: 20, height: '100%', + marginTop: 120, }, emptyText: { fontSize: 14, @@ -222,7 +224,11 @@ export const QuoteList = (props: Omit) => { containerStyle={styles.radioContainer} /> - + ); diff --git a/apps/mobile/src/screens/Bridge/components/BridgeReceiveDetail.tsx b/apps/mobile/src/screens/Bridge/components/BridgeReceiveDetail.tsx index cb0f2b599..f34874a1a 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeReceiveDetail.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeReceiveDetail.tsx @@ -75,17 +75,12 @@ interface ReceiveDetailsProps { aggregatorId: string; }; openQuotesList: () => void; - isEmptyQuote?: boolean; + noBestQuote?: boolean; } export const BridgeReceiveDetails = (props: ReceiveDetailsProps) => { const { t } = useTranslation(); - const { - activeProvider, - bestQuoteId, - openQuotesList, - isEmptyQuote, - ...other - } = props; + const { activeProvider, bestQuoteId, openQuotesList, noBestQuote, ...other } = + props; const colors = useThemeColors(); const styles = React.useMemo(() => getStyles(colors), [colors]); @@ -99,7 +94,7 @@ export const BridgeReceiveDetails = (props: ReceiveDetailsProps) => { ); if (!activeProvider) { - if (!isEmptyQuote) { + if (!noBestQuote) { return null; } return ( diff --git a/apps/mobile/src/screens/Bridge/components/BridgeTokenPair.tsx b/apps/mobile/src/screens/Bridge/components/BridgeTokenPair.tsx index ec0a8e107..dbe4bff3d 100644 --- a/apps/mobile/src/screens/Bridge/components/BridgeTokenPair.tsx +++ b/apps/mobile/src/screens/Bridge/components/BridgeTokenPair.tsx @@ -139,7 +139,6 @@ const getStyles = createGetStyles(colors => { alignItems: 'center', }, tokenText: { - marginLeft: 12, fontSize: 16, fontWeight: '500', color: colors['neutral-title1'], @@ -203,16 +202,7 @@ const TokenPairItem = (props: { setTipVisible(false)} - content={ - - {t('page.gasTopUp.InsufficientBalanceTips')} - - }> + content={t('page.gasTopUp.InsufficientBalanceTips')}> - + {getTokenSymbol(tokenPair.from_token)} diff --git a/apps/mobile/src/screens/Bridge/components/loading.tsx b/apps/mobile/src/screens/Bridge/components/loading.tsx index 34452fd0f..6c9d0b0fe 100644 --- a/apps/mobile/src/screens/Bridge/components/loading.tsx +++ b/apps/mobile/src/screens/Bridge/components/loading.tsx @@ -87,7 +87,6 @@ const SvgComponent = ({ ...props }) => ( ); export const BestQuoteLoading = () => { - const { t } = useTranslation(); const [animation] = React.useState(new Animated.Value(0)); const colors = useThemeColors(); const styles = React.useMemo(() => getStyles(colors), [colors]); @@ -96,7 +95,7 @@ export const BestQuoteLoading = () => { Animated.loop( Animated.timing(animation, { toValue: 1, - duration: 2000, + duration: 3000, useNativeDriver: true, isInteraction: false, delay: 0, @@ -114,7 +113,7 @@ export const BestQuoteLoading = () => { ? [2, 2, 1, 1, 1, 1, 2, 2] : index === 1 ? [1, 1, 1, 1, 2, 2, 1, 1] - : [1, 1, 2, 2, 1, 1, 1, 1], + : [1, 1, 2, 2, 1, 1, 0, 0], }), transform: [ @@ -253,8 +252,8 @@ const getStyles = createGetStyles(colors => ({ flexDirection: 'row', marginLeft: 16, position: 'relative', - top: -4, - left: -2, + top: -6, + left: -10, }, dot: { fontSize: 24, diff --git a/apps/mobile/src/screens/Bridge/hooks/token.tsx b/apps/mobile/src/screens/Bridge/hooks/token.tsx index 380d58ca3..2db2eb45d 100644 --- a/apps/mobile/src/screens/Bridge/hooks/token.tsx +++ b/apps/mobile/src/screens/Bridge/hooks/token.tsx @@ -10,9 +10,8 @@ import { } from './context'; import { findChain } from '@/utils/chain'; -import { CHAINS_ENUM } from '@debank/common'; +import { CHAINS, CHAINS_ENUM } from '@debank/common'; import { openapi } from '@/core/request'; -import { CHAINS } from '@/constant/chains'; import { bridgeService } from '@/core/services'; import { useAsyncInitializeChainList } from '@/hooks/useChain'; import { useAggregatorsList, useBridgeSupportedChains } from './atom'; @@ -23,7 +22,7 @@ import { addressUtils } from '@rabby-wallet/base-utils'; import { ETH_USDT_CONTRACT } from '@/constant/swap'; import useAsync from 'react-use/lib/useAsync'; import useDebounce from 'react-use/lib/useDebounce'; -import useDebounceValue from '@/hooks/common/useDebounceValue'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; const { isSameAddress } = addressUtils; @@ -165,9 +164,7 @@ export const useTokenPair = (userAddress: string) => { bridgeService.setSelectedToToken(receiveToken); }, [receiveToken]); - const [inputAmount, setPayAmount] = useState(''); - - const debouncePayAmount = useDebounceValue(inputAmount, 300); + const [payAmount, setPayAmount] = useState(''); const [selectedBridgeQuote, setOriSelectedBridgeQuote] = useState< SelectedBridgeQuote | undefined @@ -193,9 +190,9 @@ export const useTokenPair = (userAddress: string) => { const inSufficient = useMemo( () => payToken - ? tokenAmountBn(payToken).lt(debouncePayAmount) - : new BigNumber(0).lt(debouncePayAmount), - [payToken, debouncePayAmount], + ? tokenAmountBn(payToken).lt(payAmount) + : new BigNumber(0).lt(payAmount), + [payToken, payAmount], ); const [quoteList, setQuotesList] = useState([]); @@ -204,7 +201,7 @@ export const useTokenPair = (userAddress: string) => { setQuotesList([]); setSelectedBridgeQuote(undefined); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [payToken?.id, receiveToken?.id, chain, debouncePayAmount, inSufficient]); + }, [payToken?.id, receiveToken?.id, chain, payAmount, inSufficient]); const visible = useQuoteVisible(); @@ -218,166 +215,201 @@ export const useTokenPair = (userAddress: string) => { const aggregatorsList = useAggregatorsList(); + const [noBestQuote, setNoBestQuote] = useState(false); + const fetchIdRef = useRef(0); - const [isEmptyQuote, setIsEmptyQuote] = useState(false); + const inFetching = useRef(false); - const { loading: quoteLoading, error: quotesError } = useAsync(async () => { - if ( - !inSufficient && - userAddress && - payToken?.id && - receiveToken?.id && - receiveToken && - chain && - Number(debouncePayAmount) > 0 && - aggregatorsList.length > 0 - ) { - fetchIdRef.current += 1; - const currentFetchId = fetchIdRef.current; - - let preQuotesIsEmpty = false; - const result: SelectedBridgeQuote[] = []; - setIsEmptyQuote(false); + const [{ loading: quoteLoading, error: quotesError }, callGetQuotes] = + useAsyncFn(async () => { + if ( + !inSufficient && + userAddress && + payToken?.id && + receiveToken?.id && + receiveToken && + chain && + Number(payAmount) > 0 && + aggregatorsList.length > 0 + ) { + inFetching.current = true; - setQuotesList(e => { - if (!e.length) { - preQuotesIsEmpty = true; - } - return e?.map(e => ({ ...e, loading: true })); - }); + fetchIdRef.current += 1; + const currentFetchId = fetchIdRef.current; - setSelectedBridgeQuote(undefined); + let preQuotesIsEmpty = false; + const result: SelectedBridgeQuote[] = []; - const originData = await openapi - .getBridgeQuoteList({ - aggregator_ids: aggregatorsList.map(e => e.id).join(','), - from_token_id: payToken.id, - user_addr: userAddress, - from_chain_id: payToken.chain, - from_token_raw_amount: new BigNumber(debouncePayAmount) - .times(10 ** payToken.decimals) - .toFixed(0, 1) - .toString(), - to_chain_id: receiveToken.chain, - to_token_id: receiveToken.id, - }) - .catch(err => { - console.log('err', err); - setIsEmptyQuote(true); - if (currentFetchId === fetchIdRef.current) { - stats.report('bridgeQuoteResult', { - aggregatorIds: aggregatorsList.map(e => e.id).join(','), - fromChainId: payToken.chain, - fromTokenId: payToken.id, - toTokenId: receiveToken.id, - toChainId: receiveToken.chain, - status: 'fail', - amount: debouncePayAmount, - }); + setQuotesList(e => { + if (!e.length) { + preQuotesIsEmpty = true; } - }) - .finally(() => {}); - - const data = originData?.filter( - quote => - !!quote?.bridge && - !!quote?.bridge?.id && - !!quote?.bridge?.logo_url && - !!quote.bridge.name, - ); - - if (currentFetchId === fetchIdRef.current) { - stats.report('bridgeQuoteResult', { - aggregatorIds: aggregatorsList.map(e => e.id).join(','), - fromChainId: payToken.chain, - fromTokenId: payToken.id, - toTokenId: receiveToken.id, - toChainId: receiveToken.chain, - status: data ? (data?.length === 0 ? 'none' : 'success') : 'fail', + return e?.map(e => ({ ...e, loading: true })); }); - } - if (data && currentFetchId === fetchIdRef.current) { - if (!preQuotesIsEmpty) { - setQuotesList(data.map(e => ({ ...e, loading: true }))); + setSelectedBridgeQuote(undefined); + + const originData = await openapi + .getBridgeQuoteList({ + aggregator_ids: aggregatorsList.map(e => e.id).join(','), + from_token_id: payToken.id, + user_addr: userAddress, + from_chain_id: payToken.chain, + from_token_raw_amount: new BigNumber(payAmount) + .times(10 ** payToken.decimals) + .toFixed(0, 1) + .toString(), + to_chain_id: receiveToken.chain, + to_token_id: receiveToken.id, + }) + .catch(err => { + console.log('err', err); + if (currentFetchId === fetchIdRef.current) { + stats.report('bridgeQuoteResult', { + aggregatorIds: aggregatorsList.map(e => e.id).join(','), + fromChainId: payToken.chain, + fromTokenId: payToken.id, + toTokenId: receiveToken.id, + toChainId: receiveToken.chain, + status: 'fail', + amount: payAmount, + }); + } + }) + .finally(() => {}); + + const data = originData?.filter( + quote => + !!quote?.bridge && + !!quote?.bridge?.id && + !!quote?.bridge?.logo_url && + !!quote.bridge.name, + ); + + if (currentFetchId === fetchIdRef.current) { + stats.report('bridgeQuoteResult', { + aggregatorIds: aggregatorsList.map(e => e.id).join(','), + fromChainId: payToken.chain, + fromTokenId: payToken.id, + toTokenId: receiveToken.id, + toChainId: receiveToken.chain, + status: data ? (data?.length === 0 ? 'none' : 'success') : 'fail', + }); } - await Promise.allSettled( - data.map(async quote => { - if (currentFetchId !== fetchIdRef.current) { - return; - } - let tokenApproved = false; - let allowance = '0'; - const fromChain = findChain({ serverId: payToken?.chain }); - if (payToken?.id === fromChain?.nativeTokenAddress) { - tokenApproved = true; - } else { - allowance = await getERC20Allowance( - payToken.chain, - payToken.id, - quote.approve_contract_id, - ); - tokenApproved = new BigNumber(allowance).gte( - new BigNumber(debouncePayAmount).times(10 ** payToken.decimals), - ); - } - let shouldTwoStepApprove = false; - if ( - fromChain?.enum === CHAINS_ENUM.ETH && - isSameAddress(payToken.id, ETH_USDT_CONTRACT) && - Number(allowance) !== 0 && - !tokenApproved - ) { - shouldTwoStepApprove = true; - } + if (data && currentFetchId === fetchIdRef.current) { + if (!preQuotesIsEmpty) { + setQuotesList(data.map(e => ({ ...e, loading: true }))); + } - if (preQuotesIsEmpty) { - result.push({ - ...quote, - shouldTwoStepApprove, - shouldApproveToken: !tokenApproved, - }); - } else { - if (currentFetchId === fetchIdRef.current) { - setQuotesList(e => { - const filteredArr = e.filter( - item => - item.aggregator.id !== quote.aggregator.id || - item.bridge.id !== quote.bridge.id, - ); - return [ - ...filteredArr, - { - ...quote, - loading: false, - shouldTwoStepApprove, - shouldApproveToken: !tokenApproved, - }, - ]; + await Promise.allSettled( + data.map(async quote => { + if (currentFetchId !== fetchIdRef.current) { + return; + } + let tokenApproved = false; + let allowance = '0'; + const fromChain = findChain({ serverId: payToken?.chain }); + if (payToken?.id === fromChain?.nativeTokenAddress) { + tokenApproved = true; + } else { + allowance = await getERC20Allowance( + payToken.chain, + payToken.id, + quote.approve_contract_id, + ); + tokenApproved = new BigNumber(allowance).gte( + new BigNumber(payAmount).times(10 ** payToken.decimals), + ); + } + let shouldTwoStepApprove = false; + if ( + fromChain?.enum === CHAINS_ENUM.ETH && + isSameAddress(payToken.id, ETH_USDT_CONTRACT) && + Number(allowance) !== 0 && + !tokenApproved + ) { + shouldTwoStepApprove = true; + } + + if (preQuotesIsEmpty) { + result.push({ + ...quote, + shouldTwoStepApprove, + shouldApproveToken: !tokenApproved, }); + } else { + if (currentFetchId === fetchIdRef.current) { + setQuotesList(e => { + const filteredArr = e.filter( + item => + item.aggregator.id !== quote.aggregator.id || + item.bridge.id !== quote.bridge.id, + ); + return [ + ...filteredArr, + { + ...quote, + loading: false, + shouldTwoStepApprove, + shouldApproveToken: !tokenApproved, + }, + ]; + }); + } } - } - }), - ); + }), + ); - if (preQuotesIsEmpty && currentFetchId === fetchIdRef.current) { - setQuotesList(result); + if (preQuotesIsEmpty && currentFetchId === fetchIdRef.current) { + setQuotesList(result); + } } } + }, [ + inSufficient, + aggregatorsList, + refreshId, + userAddress, + payToken?.id, + receiveToken?.id, + chain, + payAmount, + ]); + + const [stateChangeLoading, setStateChangeLoading] = useState(false); + + useEffect(() => { + if ( + !inSufficient && + userAddress && + payToken?.id && + receiveToken?.id && + chain && + Number(payAmount) > 0 && + aggregatorsList.length > 0 + ) { + setStateChangeLoading(true); } }, [ + aggregatorsList.length, + chain, inSufficient, - aggregatorsList, - refreshId, - userAddress, + payAmount, payToken?.id, - receiveToken?.id, - chain, - debouncePayAmount, + receiveToken, + userAddress, ]); + useDebounce( + () => { + callGetQuotes(); + }, + 300, + [callGetQuotes], + ); + const [bestQuoteId, setBestQuoteId] = useState< | { bridgeId: string; @@ -394,21 +426,13 @@ export const useTokenPair = (userAddress: string) => { openQuote(true); }, [openQuote, setRefreshId]); - const notAvailableQuote = useMemo( - () => - !quoteLoading && - !!receiveToken && - Number(debouncePayAmount) > 0 && - (!quoteList || !quoteList.length), - [debouncePayAmount, quoteList, quoteLoading, receiveToken], - ); - useEffect(() => { if ( !quoteLoading && receiveToken && - Number(debouncePayAmount) > 0 && - quoteList.every(e => !e.loading) + Number(payAmount) > 0 && + quoteList.every(e => !e.loading) && + inFetching.current ) { const sortedList = quoteList?.sort((b, a) => { return new BigNumber(a.to_token_amount) @@ -426,6 +450,8 @@ export const useTokenPair = (userAddress: string) => { sortedList[0]?.bridge_id && sortedList[0]?.aggregator?.id ) { + setNoBestQuote(false); + setBestQuoteId({ bridgeId: sortedList[0]?.bridge_id, aggregatorId: sortedList[0]?.aggregator?.id, @@ -434,14 +460,23 @@ export const useTokenPair = (userAddress: string) => { setSelectedBridgeQuote(preItem => preItem?.manualClick ? preItem : sortedList[0], ); + } else { + setNoBestQuote(true); } + + inFetching.current = false; + setStateChangeLoading(false); + + return () => { + setNoBestQuote(false); + }; } }, [ quoteList, quoteLoading, receiveToken, setSelectedBridgeQuote, - debouncePayAmount, + payAmount, ]); if (quotesError) { @@ -452,7 +487,7 @@ export const useTokenPair = (userAddress: string) => { setExpired(false); setSelectedBridgeQuote(undefined); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [payToken?.id, receiveToken?.id, chain, debouncePayAmount, inSufficient]); + }, [payToken?.id, receiveToken?.id, chain, payAmount, inSufficient]); return { chain, @@ -465,20 +500,18 @@ export const useTokenPair = (userAddress: string) => { handleAmountChange, handleBalance, - inputAmount, - debouncePayAmount, + payAmount, inSufficient, + noBestQuote: noBestQuote, - quoteLoading, + quoteLoading: stateChangeLoading, quoteList, selectedBridgeQuote, setSelectedBridgeQuote, openQuotesList, bestQuoteId, - - notAvailableQuote, expired, }; };