From 600784e7bfdd2618c87fd2d8f1d7c39ba5446f18 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 9 May 2023 18:24:27 +0200 Subject: [PATCH] feat: parse amounts on QR scan with regular send button (#4461) --- src/components/Modals/Send/Form.tsx | 19 ++++++++++++++++--- .../hooks/useSendDetails/useSendDetails.tsx | 14 +++++++++----- .../Send/hooks/useSendFees/useSendFees.tsx | 9 ++++++--- src/components/Modals/Send/views/Details.tsx | 16 ++++++++++++---- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/components/Modals/Send/Form.tsx b/src/components/Modals/Send/Form.tsx index 3093cb7287b..6218fa05a7b 100644 --- a/src/components/Modals/Send/Form.tsx +++ b/src/components/Modals/Send/Form.tsx @@ -7,8 +7,10 @@ import { FormProvider, useForm } from 'react-hook-form' import { Redirect, Route, Switch, useHistory, useLocation } from 'react-router-dom' import { QrCodeScanner } from 'components/QrCodeScanner/QrCodeScanner' import { SelectAssetRouter } from 'components/SelectAssets/SelectAssetRouter' -import { selectSelectedCurrency } from 'state/slices/selectors' -import { useAppSelector } from 'state/store' +import { parseMaybeUrl } from 'lib/address/address' +import { bnOrZero } from 'lib/bignumber/bignumber' +import { selectMarketDataById, selectSelectedCurrency } from 'state/slices/selectors' +import { store, useAppSelector } from 'state/store' import { useFormSend } from './hooks/useFormSend/useFormSend' import { SendFormFields, SendRoutes } from './SendCommon' @@ -81,8 +83,19 @@ export const Form: React.FC = ({ initialAssetId, accountId }) => }, []) const handleQrSuccess = useCallback( - (decodedText: string) => { + async (decodedText: string) => { methods.setValue(SendFormFields.Input, decodedText.trim()) + + const maybeUrlResult = await parseMaybeUrl({ value: decodedText }) + if (maybeUrlResult.assetId && maybeUrlResult.amountCryptoPrecision) { + const marketData = selectMarketDataById(store.getState(), maybeUrlResult.assetId ?? '') + methods.setValue(SendFormFields.CryptoAmount, maybeUrlResult.amountCryptoPrecision) + methods.setValue( + SendFormFields.FiatAmount, + bnOrZero(maybeUrlResult.amountCryptoPrecision).times(marketData.price).toString(), + ) + } + history.push(SendRoutes.Address) }, [history, methods], diff --git a/src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx b/src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx index 0ee886ea0aa..892e732570b 100644 --- a/src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx +++ b/src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx @@ -66,6 +66,10 @@ export const useSendDetails = (): UseSendDetailsReturnType => { name: SendFormFields.AccountId, }) + const cryptoAmount = useWatch({ + name: SendFormFields.CryptoAmount, + }) + const price = bnOrZero(useAppSelector(state => selectMarketDataById(state, assetId)).price) const chainAdapterManager = getChainAdapterManager() @@ -113,18 +117,17 @@ export const useSendDetails = (): UseSendDetailsReturnType => { const estimateFormFees = useCallback((): Promise> => { if (!asset) throw new Error('No asset found') - const { cryptoAmount, assetId, to, sendMax, accountId } = getValues() + const { assetId, to, sendMax } = getValues() if (!wallet) throw new Error('No wallet connected') return estimateFees({ cryptoAmount, assetId, to, sendMax, accountId, contractAddress }) - }, [asset, contractAddress, getValues, wallet]) + }, [accountId, asset, contractAddress, cryptoAmount, getValues, wallet]) const debouncedSetEstimatedFormFees = useMemo(() => { return debounce( async () => { - if (!asset) return + if (!asset || !accountId) return const estimatedFees = await estimateFormFees() - const { cryptoAmount } = getValues() const hasValidBalance = cryptoHumanBalance.gte(cryptoAmount) if (!hasValidBalance) { @@ -173,13 +176,14 @@ export const useSendDetails = (): UseSendDetailsReturnType => { { leading: true, trailing: true }, ) }, [ + accountId, asset, assetId, + cryptoAmount, cryptoHumanBalance, estimateFormFees, feeAsset.assetId, feeAsset.symbol, - getValues, nativeAssetBalance, setValue, ]) diff --git a/src/components/Modals/Send/hooks/useSendFees/useSendFees.tsx b/src/components/Modals/Send/hooks/useSendFees/useSendFees.tsx index b5933b5fb16..2ffbf3178df 100644 --- a/src/components/Modals/Send/hooks/useSendFees/useSendFees.tsx +++ b/src/components/Modals/Send/hooks/useSendFees/useSendFees.tsx @@ -13,7 +13,7 @@ import type { FeePrice } from '../../views/Confirm' export const useSendFees = () => { const [fees, setFees] = useState(null) const { control } = useFormContext() - const { assetId, estimatedFees } = useWatch({ + const { assetId, estimatedFees, cryptoAmount } = useWatch({ control, }) const feeAssetId = getChainAdapterManager().get(fromAssetId(assetId).chainId)?.getFeeAssetId() @@ -59,9 +59,12 @@ export const useSendFees = () => { ) setFees(txFees) } - // We only want this effect to run on mount or when the estimatedFees in state change + // We only want this effect to run on + // - mount + // - when the estimatedFees reference invalidates + // - when cryptoAmount reference invalidates, since this wouldn't invalidate in the context of QR codes with amounts otherwise // eslint-disable-next-line react-hooks/exhaustive-deps - }, [estimatedFees, assetId]) + }, [estimatedFees, cryptoAmount, assetId]) return { fees } } diff --git a/src/components/Modals/Send/views/Details.tsx b/src/components/Modals/Send/views/Details.tsx index edeb8c8b606..768ffa9597e 100644 --- a/src/components/Modals/Send/views/Details.tsx +++ b/src/components/Modals/Send/views/Details.tsx @@ -14,6 +14,7 @@ import { ModalHeader, Stack, Tooltip, + usePrevious, } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip' @@ -44,7 +45,7 @@ import { SendMaxButton } from '../SendMaxButton/SendMaxButton' const MAX_COSMOS_SDK_MEMO_LENGTH = 256 export const Details = () => { - const { control, setValue } = useFormContext() + const { control, setValue, trigger } = useFormContext() const history = useHistory() const translate = useTranslate() @@ -80,11 +81,18 @@ export const Details = () => { state: { wallet }, } = useWallet() + const previousAccountId = usePrevious(accountId) useEffect(() => { - // Initial setting of cryptoAmount in case of a QR-code set amount - if (!cryptoAmount) handleInputChange(cryptoAmount ?? '0') + // This component initially mounts without an accountId, because of how works + // Also turns out we don't handle re-validation in case of changing AccountIds + // This effect takes care of both the initial/account change cases + if (previousAccountId !== accountId) { + const inputAmount = fieldName === SendFormFields.CryptoAmount ? cryptoAmount : fiatAmount + handleInputChange(inputAmount ?? '0') + trigger(fieldName) + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [handleInputChange]) + }, [accountId]) const asset = useAppSelector(state => selectAssetById(state, assetId ?? ''))