From 147e2d95f6b9d1aafb69a3d151a17320b4a5773c Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Thu, 17 Oct 2024 09:29:02 +0100 Subject: [PATCH] Update token transfer value in use value hook --- .../info/hooks/use-token-values.test.ts | 148 +++++++++--------- .../confirm/info/hooks/use-token-values.ts | 87 +++++----- .../__snapshots__/send-heading.test.tsx.snap | 49 ++++-- .../info/shared/send-heading/send-heading.tsx | 15 +- .../token-transfer.test.tsx.snap | 49 ++++-- 5 files changed, 202 insertions(+), 146 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts index 7ac4aa5b5c92..1ed5e9c249ff 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts @@ -1,120 +1,126 @@ import { TransactionMeta } from '@metamask/transaction-controller'; +import { Numeric } from '../../../../../../../shared/modules/Numeric'; import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer'; import mockState from '../../../../../../../test/data/mock-state.json'; -import { renderHookWithProvider } from '../../../../../../../test/lib/render-helpers'; -// import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; -import { Numeric } from '../../../../../../../shared/modules/Numeric'; +import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; -import { useTokenTracker } from '../../../../../../hooks/useTokenTracker'; +import { useAssetDetails } from '../../../../hooks/useAssetDetails'; import { useTokenValues } from './use-token-values'; +import { useDecodedTransactionData } from './useDecodedTransactionData'; + +jest.mock('../../../../hooks/useAssetDetails', () => ({ + ...jest.requireActual('../../../../hooks/useAssetDetails'), + useAssetDetails: jest.fn(), +})); + +jest.mock('./useDecodedTransactionData', () => ({ + ...jest.requireActual('./useDecodedTransactionData'), + useDecodedTransactionData: jest.fn(), +})); jest.mock( '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate', () => jest.fn(), ); -jest.mock('../../../../../../hooks/useTokenTracker', () => ({ - ...jest.requireActual('../../../../../../hooks/useTokenTracker'), - useTokenTracker: jest.fn(), -})); - describe('useTokenValues', () => { + const useAssetDetailsMock = jest.mocked(useAssetDetails); + const useDecodedTransactionDataMock = jest.mocked(useDecodedTransactionData); const useTokenExchangeRateMock = jest.mocked(useTokenExchangeRate); - const useTokenTrackerMock = jest.mocked(useTokenTracker); - const TEST_SELECTED_TOKEN = { - address: 'address', - decimals: 18, - symbol: 'symbol', - iconUrl: 'iconUrl', - image: 'image', - }; - - it('returns native and fiat balances', async () => { - (useTokenTrackerMock as jest.Mock).mockResolvedValue({ - tokensWithBalances: [ - { - address: '0x076146c765189d51be3160a2140cf80bfc73ad68', - balance: '1000000000000000000', - decimals: 18, - }, - ], - }); - - (useTokenExchangeRateMock as jest.Mock).mockResolvedValue( - new Numeric(1, 10), - ); - - const transactionMeta = genUnapprovedTokenTransferConfirmation( - {}, - ) as TransactionMeta; - - const { result, waitForNextUpdate } = renderHookWithProvider( - () => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN), - mockState, - ); - - await waitForNextUpdate(); - - expect(result.current).toEqual({ - fiatDisplayValue: '$1.00', - tokenBalance: '1', - }); + beforeEach(() => { + jest.resetAllMocks(); }); - it('returns undefined native and fiat balances if no token with balances is returned', async () => { - (useTokenTrackerMock as jest.Mock).mockResolvedValue({ - tokensWithBalances: [], - }); - + it('returns native and fiat balances', async () => { + (useAssetDetailsMock as jest.Mock).mockImplementation(() => ({ + decimals: '10', + })); + (useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({ + pending: false, + value: { + data: [ + { + name: 'transfer', + params: [ + { + type: 'address', + value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', + }, + { + type: 'uint256', + value: 70000000000, + }, + ], + }, + ], + source: 'FourByte', + }, + })); (useTokenExchangeRateMock as jest.Mock).mockResolvedValue( - new Numeric(1, 10), + new Numeric(0.91, 10), ); const transactionMeta = genUnapprovedTokenTransferConfirmation( {}, ) as TransactionMeta; - const { result, waitForNextUpdate } = renderHookWithProvider( - () => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN), + const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider( + () => useTokenValues(transactionMeta), mockState, ); await waitForNextUpdate(); expect(result.current).toEqual({ - fiatDisplayValue: undefined, - tokenBalance: undefined, + decodedTransferValue: 7, + fiatDisplayValue: '$6.37', + pending: false, }); }); it('returns undefined fiat balance if no token rate is returned', async () => { - (useTokenTrackerMock as jest.Mock).mockResolvedValue({ - tokensWithBalances: [ - { - address: '0x076146c765189d51be3160a2140cf80bfc73ad68', - balance: '1000000000000000000', - decimals: 18, - }, - ], - }); - + (useAssetDetailsMock as jest.Mock).mockImplementation(() => ({ + decimals: '10', + })); + (useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({ + pending: false, + value: { + data: [ + { + name: 'transfer', + params: [ + { + type: 'address', + value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', + }, + { + type: 'uint256', + value: 70000000000, + }, + ], + }, + ], + source: 'FourByte', + }, + })); (useTokenExchangeRateMock as jest.Mock).mockResolvedValue(null); const transactionMeta = genUnapprovedTokenTransferConfirmation( {}, ) as TransactionMeta; - const { result, waitForNextUpdate } = renderHookWithProvider( - () => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN), + const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider( + () => useTokenValues(transactionMeta), mockState, ); await waitForNextUpdate(); expect(result.current).toEqual({ + decodedTransferValue: 7, fiatDisplayValue: null, - tokenBalance: '1', + pending: false, }); }); }); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts index 9515a45515bf..232743e5cdba 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts @@ -1,38 +1,44 @@ import { TransactionMeta } from '@metamask/transaction-controller'; +import { isHexString } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; +import { isBoolean } from 'lodash'; import { useMemo, useState } from 'react'; -import { calcTokenAmount } from '../../../../../../../shared/lib/transactions-controller-utils'; -import { toChecksumHexAddress } from '../../../../../../../shared/modules/hexstring-utils'; import { Numeric } from '../../../../../../../shared/modules/Numeric'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { useFiatFormatter } from '../../../../../../hooks/useFiatFormatter'; -import { useTokenTracker } from '../../../../../../hooks/useTokenTracker'; -import { SelectedToken } from '../shared/selected-token'; +import { useAssetDetails } from '../../../../hooks/useAssetDetails'; +import { useDecodedTransactionData } from './useDecodedTransactionData'; -export const useTokenValues = ( - transactionMeta: TransactionMeta, - selectedToken: SelectedToken, -) => { - const [tokensWithBalances, setTokensWithBalances] = useState< - { balance: string; address: string; decimals: number; string: string }[] - >([]); +export const useTokenValues = (transactionMeta: TransactionMeta) => { + const { decimals } = useAssetDetails( + transactionMeta.txParams.to, + transactionMeta.txParams.from, + transactionMeta.txParams.data, + ); - const fetchTokenBalances = async () => { - const result: { - tokensWithBalances: { - balance: string; - address: string; - decimals: number; - string: string; - }[]; - } = await useTokenTracker({ - tokens: [selectedToken], - address: undefined, - }); + const decodedResponse = useDecodedTransactionData(); + const { value, pending } = decodedResponse; - setTokensWithBalances(result.tokensWithBalances); - }; + const decodedTransferValue = useMemo(() => { + if (!value) { + return 0; + } - fetchTokenBalances(); + const paramIndex = value.data[0].params.findIndex( + (param) => + param.value !== undefined && + !isHexString(param.value) && + param.value.length === undefined && + !isBoolean(param.value), + ); + if (paramIndex === -1) { + return 0; + } + + return new BigNumber(value.data[0].params[paramIndex].value.toString()) + .dividedBy(new BigNumber(10).pow(Number(decimals))) + .toNumber(); + }, [value, decimals]); const [exchangeRate, setExchangeRate] = useState(); const fetchExchangeRate = async () => { @@ -40,38 +46,19 @@ export const useTokenValues = ( setExchangeRate(result); }; - fetchExchangeRate(); - const tokenBalance = useMemo(() => { - const tokenWithBalance = tokensWithBalances.find( - (token: { - balance: string; - address: string; - decimals: number; - string: string; - }) => - toChecksumHexAddress(token.address) === - toChecksumHexAddress(transactionMeta?.txParams?.to as string), - ); - - if (!tokenWithBalance) { - return undefined; - } - - return calcTokenAmount(tokenWithBalance.balance, tokenWithBalance.decimals); - }, [tokensWithBalances]); - const fiatValue = - exchangeRate && tokenBalance && exchangeRate.times(tokenBalance).toNumber(); - + exchangeRate && + decodedTransferValue && + exchangeRate.times(decodedTransferValue, 10).toNumber(); const fiatFormatter = useFiatFormatter(); - const fiatDisplayValue = fiatValue && fiatFormatter(fiatValue, { shorten: true }); return { + decodedTransferValue, fiatDisplayValue, - tokenBalance: tokenBalance && String(tokenBalance.toNumber()), + pending, }; }; diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap index 990c17e5c49e..677a5a357155 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap @@ -3,18 +3,47 @@ exports[` renders component 1`] = `
-
- ? -
-

- Unknown -

+ + + + + + + +
`; diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx index e1c19a64d692..2806c33936c0 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx @@ -22,6 +22,7 @@ import { MultichainState } from '../../../../../../../selectors/multichain'; import { useConfirmContext } from '../../../../../context/confirm'; import { useTokenImage } from '../../hooks/use-token-image'; import { useTokenValues } from '../../hooks/use-token-values'; +import { ConfirmLoader } from '../confirm-loader/confirm-loader'; const SendHeading = () => { const t = useI18nContext(); @@ -31,10 +32,8 @@ const SendHeading = () => { getWatchedToken(transactionMeta)(state), ); const { tokenImage } = useTokenImage(transactionMeta, selectedToken); - const { tokenBalance, fiatDisplayValue } = useTokenValues( - transactionMeta, - selectedToken, - ); + const { decodedTransferValue, fiatDisplayValue, pending } = + useTokenValues(transactionMeta); const TokenImage = ( { variant={TextVariant.headingLg} color={TextColor.inherit} marginTop={3} - >{`${tokenBalance || ''} ${selectedToken?.symbol || t('unknown')}`} + >{`${decodedTransferValue || ''} ${ + selectedToken?.symbol || t('unknown') + }`} {fiatDisplayValue && ( {fiatDisplayValue} @@ -67,6 +68,10 @@ const SendHeading = () => { ); + if (pending) { + return ; + } + return (
-
- ? -
-

- Unknown -

+ + + + + + + +