diff --git a/app/components/UI/NetworkAssetLogo/index.tsx b/app/components/UI/NetworkAssetLogo/index.tsx
index 90629c5f871d..7f68860d5cbd 100644
--- a/app/components/UI/NetworkAssetLogo/index.tsx
+++ b/app/components/UI/NetworkAssetLogo/index.tsx
@@ -3,20 +3,20 @@ import { ChainId } from '@metamask/controller-utils';
import TokenIcon from '../Swaps/components/TokenIcon';
interface NetworkAssetLogoProps {
- chainId: string;
- ticker: string;
- style: object;
big: boolean;
biggest: boolean;
- testID: string;
+ chainId: string;
+ style: object;
+ ticker: string;
+ testID?: string;
}
function NetworkAssetLogo({
- chainId,
- ticker,
- style,
big,
biggest,
+ chainId,
+ style,
+ ticker,
testID,
}: NetworkAssetLogoProps) {
if (chainId === ChainId.mainnet) {
@@ -34,8 +34,8 @@ function NetworkAssetLogo({
);
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.styles.ts b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.styles.ts
new file mode 100644
index 000000000000..73d203c32024
--- /dev/null
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.styles.ts
@@ -0,0 +1,18 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../../../../util/theme/models';
+
+export const styleSheet = (params: {
+ theme: Theme;
+}) => {
+ const { theme } = params;
+ return StyleSheet.create({
+ avatarToken: {
+ backgroundColor: theme.colors.background.default,
+ borderRadius: 99,
+ },
+ base: {
+ justifyContent: 'center',
+ flexDirection: 'row',
+ },
+ });
+};
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.tsx b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.tsx
new file mode 100644
index 000000000000..ce73cc6736b8
--- /dev/null
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/avatar-token-with-network-badge.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { TransactionMeta } from '@metamask/transaction-controller';
+import { Hex } from '@metamask/utils';
+
+import { strings } from '../../../../../../../../../locales/i18n';
+import { AvatarSize } from '../../../../../../../../component-library/components/Avatars/Avatar/Avatar.types';
+import AvatarToken from '../../../../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken/AvatarToken';
+import Badge, {
+ BadgeVariant,
+} from '../../../../../../../../component-library/components/Badges/Badge';
+import BadgeWrapper, {
+ BadgePosition,
+} from '../../../../../../../../component-library/components/Badges/BadgeWrapper';
+import { useStyles } from '../../../../../../../../component-library/hooks';
+import NetworkAssetLogo from '../../../../../../../UI/NetworkAssetLogo';
+import { TokenI } from '../../../../../../../UI/Tokens/types';
+import useNetworkInfo from '../../../../../hooks/useNetworkInfo';
+import { useTokenAsset } from '../../../../../hooks/useTokenAsset';
+import { useTransactionMetadataRequest } from '../../../../../hooks/transactions/useTransactionMetadataRequest';
+import { styleSheet } from './avatar-token-with-network-badge.styles';
+import { View } from 'react-native';
+
+const AvatarTokenOrNetworkAssetLogo = ({
+ asset,
+ chainId,
+ displayName,
+}: {
+ asset: TokenI;
+ chainId: Hex;
+ displayName: string;
+}) => {
+ const { styles } = useStyles(styleSheet, {});
+ const { image, isNative } = asset;
+ const isUnknownToken = displayName === strings('token.unknown');
+
+ return isNative ? (
+
+ ) : (
+
+ );
+};
+
+export const AvatarTokenWithNetworkBadge = () => {
+ const { styles } = useStyles(styleSheet, {});
+ const { chainId } =
+ useTransactionMetadataRequest() ?? ({} as TransactionMeta);
+ const { asset, displayName } = useTokenAsset();
+ const { networkName, networkImage } = useNetworkInfo(chainId);
+
+ return (
+
+
+ }
+ >
+
+
+
+ );
+};
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/index.ts b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/index.ts
new file mode 100644
index 000000000000..07efbfb3c88f
--- /dev/null
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/avatar-token-with-network-badge/index.ts
@@ -0,0 +1 @@
+export { AvatarTokenWithNetworkBadge } from './avatar-token-with-network-badge';
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.styles.ts b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.styles.ts
index 3be254fdae45..fa2ffe9f72bf 100644
--- a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.styles.ts
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.styles.ts
@@ -15,19 +15,13 @@ const styleSheet = (params: {
assetAmountText: {
textAlign: 'center',
},
+ assetTextUnknown: {
+ color: theme.colors.text.alternative,
+ },
assetFiatConversionText: {
textAlign: 'center',
color: theme.colors.text.alternative,
},
- networkAndTokenContainer: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- },
- networkLogo: {
- width: 48,
- height: 48,
- },
container: {
paddingBottom: 16,
paddingTop: isFlatConfirmation ? 16 : 0,
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.test.tsx b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.test.tsx
index e56b470c1221..4c7820bf1f57 100644
--- a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.test.tsx
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import renderWithProvider, { DeepPartial } from '../../../../../../../util/test/renderWithProvider';
-import { stakingDepositConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
+import { stakingDepositConfirmationState, transferConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
import TokenHero from './token-hero';
import { fireEvent, waitFor } from '@testing-library/react-native';
import { merge } from 'lodash';
@@ -17,16 +17,52 @@ jest.mock('../../../../../../../core/Engine', () => ({
}));
describe('TokenHero', () => {
- it('contains token and fiat values for staking deposit', async () => {
- const { getByText } = renderWithProvider(, {
+ it('displays avatar, amount, and fiat values for a simple send transfer', async () => {
+ const state: DeepPartial = merge(
+ {},
+ transferConfirmationState,
+ {
+ engine: {
+ backgroundState: {
+ TransactionController: {
+ transactions: [
+ { txParams: { value: `0x${decGWEIToHexWEI(55555555)}` } },
+ ],
+ },
+ },
+ },
+ },
+ );
+ const { getByText, queryByTestId } = renderWithProvider(, { state });
+
+ await waitFor(async () => {
+ expect(queryByTestId('avatar-with-badge-avatar-token-ETH')).toBeTruthy();
+ expect(getByText('0.0556 ETH')).toBeDefined();
+ expect(getByText('$199.79')).toBeDefined();
+ });
+
+ const tokenAmountText = getByText('0.0556 ETH');
+ fireEvent.press(tokenAmountText);
+
+ await waitFor(() => {
+ expect(getByText('Amount')).toBeDefined();
+ expect(getByText('0.055555555')).toBeDefined();
+ });
+ });
+
+ it('displays avatar, amount, and fiat values for staking deposit', async () => {
+ const { getByText, queryByTestId } = renderWithProvider(, {
state: stakingDepositConfirmationState,
});
- expect(getByText('0.0001 ETH')).toBeDefined();
- expect(getByText('$0.36')).toBeDefined();
+ await waitFor(async () => {
+ expect(queryByTestId('avatar-with-badge-avatar-token-ETH')).toBeTruthy();
+ expect(getByText('0.0001 ETH')).toBeDefined();
+ expect(getByText('$0.36')).toBeDefined();
+ });
});
- it('contains token and fiat values for staking deposit', async () => {
+ it('displays avatar, rounded amount, amount, and fiat values for staking deposit', async () => {
const state: DeepPartial = merge(
{},
stakingDepositConfirmationState,
@@ -43,13 +79,16 @@ describe('TokenHero', () => {
},
);
- const { getByText } = renderWithProvider(
+ const { getByText, queryByTestId } = renderWithProvider(
,
{ state },
);
- expect(getByText('0.0123 ETH')).toBeDefined();
- expect(getByText('$44.40')).toBeDefined();
+ await waitFor(() => {
+ expect(queryByTestId('avatar-with-badge-avatar-token-ETH')).toBeTruthy();
+ expect(getByText('0.0123 ETH')).toBeDefined();
+ expect(getByText('$44.40')).toBeDefined();
+ });
const tokenAmountText = getByText('0.0123 ETH');
fireEvent.press(tokenAmountText);
diff --git a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.tsx b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.tsx
index fa423f5f5828..7d55a9869636 100644
--- a/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.tsx
+++ b/app/components/Views/confirmations/components/rows/transactions/token-hero/token-hero.tsx
@@ -2,98 +2,94 @@ import React, { useState } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { useSelector } from 'react-redux';
import { strings } from '../../../../../../../../locales/i18n';
-import Badge, {
- BadgeVariant,
-} from '../../../../../../../component-library/components/Badges/Badge';
-import BadgeWrapper, {
- BadgePosition,
-} from '../../../../../../../component-library/components/Badges/BadgeWrapper';
import Text, {
TextVariant,
} from '../../../../../../../component-library/components/Texts/Text';
import { useStyles } from '../../../../../../../component-library/hooks';
-import images from '../../../../../../../images/image-icons';
import { selectTransactionState } from '../../../../../../../reducers/transaction';
-import TokenIcon from '../../../../../../UI/Swaps/components/TokenIcon';
+import useHideFiatForTestnet from '../../../../../../hooks/useHideFiatForTestnet';
import { useConfirmationContext } from '../../../../context/confirmation-context';
-import { useTokenValues } from '../../../../hooks/useTokenValues';
import { useFlatConfirmation } from '../../../../hooks/ui/useFlatConfirmation';
+import { useTokenAsset } from '../../../../hooks/useTokenAsset';
+import { useTokenAmount } from '../../../../hooks/useTokenAmount';
import AnimatedPulse from '../../../UI/animated-pulse';
import { TooltipModal } from '../../../UI/Tooltip/Tooltip';
+import { AvatarTokenWithNetworkBadge } from './avatar-token-with-network-badge';
import styleSheet from './token-hero.styles';
-const NetworkAndTokenImage = ({
- tokenSymbol,
- styles,
-}: {
- tokenSymbol: string;
- styles: StyleSheet.NamedStyles>;
-}) => (
-
-
- }
- >
-
-
-
-);
-
const AssetAmount = ({
- tokenAmountDisplayValue,
- tokenSymbol,
- styles,
+ amount,
setIsModalVisible,
+ styles,
}: {
- tokenAmountDisplayValue: string;
- tokenSymbol: string;
- styles: StyleSheet.NamedStyles>;
+ amount?: string;
setIsModalVisible: ((isModalVisible: boolean) => void) | null;
-}) => (
-
- {setIsModalVisible ? (
- setIsModalVisible(true)}>
+ styles: StyleSheet.NamedStyles>;
+}) => {
+ const { displayName } = useTokenAsset();
+ const isUnknownToken = displayName === strings('token.unknown');
+
+ return (
+
+ {setIsModalVisible ? (
+ setIsModalVisible(true)}>
+
+ {amount}{' '}
+
+ {displayName}
+
+
+
+ ) : (
- {tokenAmountDisplayValue} {tokenSymbol}
+ {amount}{' '}
+
+ {displayName}
+
-
- ) : (
-
- {tokenAmountDisplayValue} {tokenSymbol}
-
- )}
-
-);
+ )}
+
+ );
+};
const AssetFiatConversion = ({
- fiatDisplayValue,
+ fiat,
styles,
}: {
- fiatDisplayValue: string;
+ fiat?: string;
styles: StyleSheet.NamedStyles>;
-}) => (
-
- {fiatDisplayValue}
-
-);
+}) => {
+ const hideFiatForTestnet = useHideFiatForTestnet();
+ if (hideFiatForTestnet || !fiat) {
+ return null;
+ }
+
+ return (
+
+ {fiat}
+
+ );
+};
const TokenHero = ({ amountWei }: { amountWei?: string }) => {
+ const [isModalVisible, setIsModalVisible] = useState(false);
+
const { isTransactionValueUpdating } = useConfirmationContext();
const { isFlatConfirmation } = useFlatConfirmation();
const { maxValueMode } = useSelector(selectTransactionState);
const { styles } = useStyles(styleSheet, {
isFlatConfirmation,
});
- const { tokenAmountValue, tokenAmountDisplayValue, fiatDisplayValue } =
- useTokenValues({ amountWei });
- const [isModalVisible, setIsModalVisible] = useState(false);
- const displayTokenAmountIsRounded =
- tokenAmountValue !== tokenAmountDisplayValue;
+ const { amountPrecise, amount, fiat } = useTokenAmount({ amountWei });
- const tokenSymbol = 'ETH';
+ const isRoundedAmount = amountPrecise !== amount;
return (
{
preventPulse={!maxValueMode}
>
-
+
-
- {displayTokenAmountIsRounded && (
+
+ {isRoundedAmount && (
diff --git a/app/components/Views/confirmations/constants/tokens.ts b/app/components/Views/confirmations/constants/tokens.ts
index 88f59cd469ef..716c571a1cb9 100644
--- a/app/components/Views/confirmations/constants/tokens.ts
+++ b/app/components/Views/confirmations/constants/tokens.ts
@@ -1,3 +1,5 @@
+export const NATIVE_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
+
export const TOKEN_ADDRESS = {
DAI: '0x6b175474e89094c44da98b954eedeac495271d0f',
};
diff --git a/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.test.tsx b/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.test.tsx
index c96816ec65d4..90161531c0b5 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.test.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.test.tsx
@@ -26,6 +26,11 @@ jest.mock('../../../../hooks/useConfirmActions', () => ({
useConfirmActions: jest.fn(),
}));
+jest.mock('../../../../components/UI/animated-pulse', () => ({
+ __esModule: true,
+ default: ({ children }: { children: React.ReactNode }) => <>{children}>,
+}));
+
jest.mock('../../../../components/UI/navbar/navbar', () => ({
getNavbar: jest.fn(),
}));
diff --git a/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.tsx b/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.tsx
index 13e1d54950a1..b53b44cb77af 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-claim/staking-claim.tsx
@@ -9,7 +9,7 @@ import { EVENT_PROVIDERS } from '../../../../../../UI/Stake/constants/events';
import useClearConfirmationOnBackSwipe from '../../../../hooks/ui/useClearConfirmationOnBackSwipe';
import { useConfirmationMetricEvents } from '../../../../hooks/metrics/useConfirmationMetricEvents';
import useNavbar from '../../../../hooks/ui/useNavbar';
-import { useTokenValues } from '../../../../hooks/useTokenValues';
+import { useTokenAmount } from '../../../../hooks/useTokenAmount';
import { useTransactionMetadataRequest } from '../../../../hooks/transactions/useTransactionMetadataRequest';
import InfoSection from '../../../../components/UI/info-row/info-section';
import StakingContractInteractionDetails from '../../components/staking-contract-interaction-details/staking-contract-interaction-details';
@@ -30,15 +30,20 @@ const StakingClaim = ({
const { trackPageViewedEvent, setConfirmationMetric } =
useConfirmationMetricEvents();
const amountWei = route?.params?.amountWei;
- const { tokenAmountDisplayValue } = useTokenValues({ amountWei });
+ const { amount } = useTokenAmount({ amountWei });
+
useEffect(() => {
+ if (amount === undefined) {
+ return;
+ }
+
setConfirmationMetric({
properties: {
selected_provider: EVENT_PROVIDERS.CONSENSYS,
- transaction_amount_eth: tokenAmountDisplayValue,
+ transaction_amount_eth: amount,
},
});
- }, [tokenAmountDisplayValue, setConfirmationMetric]);
+ }, [amount, setConfirmationMetric]);
useEffect(trackPageViewedEvent, [trackPageViewedEvent]);
diff --git a/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.test.tsx b/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.test.tsx
index 0d25968665ea..712478cbe4e8 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.test.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { fireEvent } from '@testing-library/react-native';
+import { fireEvent, waitFor } from '@testing-library/react-native';
import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
import { stakingDepositConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
@@ -100,30 +100,33 @@ describe('StakingDeposit', () => {
});
});
- it('tracks metrics events', () => {
+ it('tracks metrics events', async () => {
const { getByText } = renderWithProvider(, {
state: stakingDepositConfirmationState,
});
expect(mockTrackPageViewedEvent).toHaveBeenCalledTimes(1);
- // 2 calls here, 1st call is for the setting transaction_amount_eth
- // 2nd call is for the setting advanced_details_viewed
- expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(2);
- expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
- expect.objectContaining({
- properties: expect.objectContaining({
- transaction_amount_eth: '0.0001',
- selected_provider: EVENT_PROVIDERS.CONSENSYS,
+
+ await waitFor(() => {
+ // 2 calls here, 1st call is for the setting transaction_amount_eth
+ // 2nd call is for the setting advanced_details_viewed
+ expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(2);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
+ expect.objectContaining({
+ properties: expect.objectContaining({
+ transaction_amount_eth: '0.0001',
+ selected_provider: EVENT_PROVIDERS.CONSENSYS,
+ }),
}),
- }),
- );
- expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
- expect.objectContaining({
- properties: expect.objectContaining({
- advanced_details_viewed: false,
+ );
+ expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
+ expect.objectContaining({
+ properties: expect.objectContaining({
+ advanced_details_viewed: false,
+ }),
}),
- }),
- );
+ );
+ });
fireEvent.press(getByText('Advanced details'));
expect(mockTrackAdvancedDetailsToggledEvent).toHaveBeenCalledTimes(1);
diff --git a/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.tsx b/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.tsx
index 6e2baae9d18d..3e98138acaac 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-deposit/staking-deposit.tsx
@@ -4,7 +4,7 @@ import { EVENT_PROVIDERS } from '../../../../../../UI/Stake/constants/events';
import useClearConfirmationOnBackSwipe from '../../../../hooks/ui/useClearConfirmationOnBackSwipe';
import { useConfirmationMetricEvents } from '../../../../hooks/metrics/useConfirmationMetricEvents';
import useNavbar from '../../../../hooks/ui/useNavbar';
-import { useTokenValues } from '../../../../hooks/useTokenValues';
+import { useTokenAmount } from '../../../../hooks/useTokenAmount';
import InfoSectionAccordion from '../../../../components/UI/info-section-accordion';
import StakingContractInteractionDetails from '../../components/staking-contract-interaction-details/staking-contract-interaction-details';
import StakingDetails from '../../components/staking-details/staking-details';
@@ -20,15 +20,19 @@ const StakingDeposit = () => {
trackPageViewedEvent,
setConfirmationMetric,
} = useConfirmationMetricEvents();
- const { tokenAmountDisplayValue } = useTokenValues();
+ const { amount } = useTokenAmount();
useEffect(() => {
+ if (amount === undefined) {
+ return;
+ }
+
setConfirmationMetric({
properties: {
selected_provider: EVENT_PROVIDERS.CONSENSYS,
- transaction_amount_eth: tokenAmountDisplayValue,
+ transaction_amount_eth: amount,
},
});
- }, [tokenAmountDisplayValue, setConfirmationMetric]);
+ }, [amount, setConfirmationMetric]);
useEffect(() => {
trackPageViewedEvent();
diff --git a/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.test.tsx b/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.test.tsx
index 674742018334..b56a15b701e9 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.test.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.test.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { waitFor } from '@testing-library/react-native';
import { stakingWithdrawalConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
import { EVENT_PROVIDERS } from '../../../../../../UI/Stake/constants/events';
@@ -35,6 +36,11 @@ jest.mock('../../../../components/UI/navbar/navbar', () => ({
getNavbar: jest.fn(),
}));
+jest.mock('../../../../utils/token', () => ({
+ ...jest.requireActual('../../../../utils/token'),
+ fetchErc20Decimals: jest.fn().mockResolvedValue(18),
+}));
+
const noop = () => undefined;
jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
@@ -92,6 +98,7 @@ describe('StakingWithdrawal', () => {
state: stakingWithdrawalConfirmationState,
},
);
+
expect(getByText('Withdrawal time')).toBeDefined();
expect(getByText('Unstaking to')).toBeDefined();
@@ -109,7 +116,7 @@ describe('StakingWithdrawal', () => {
});
});
- it('tracks metrics events', () => {
+ it('tracks metrics events', async () => {
renderWithProvider(
{
},
);
+
expect(mockTrackPageViewedEvent).toHaveBeenCalledTimes(1);
- expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(1);
- expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
- expect.objectContaining({
- properties: expect.objectContaining({
- selected_provider: EVENT_PROVIDERS.CONSENSYS,
- transaction_amount_eth: '1',
+ await waitFor(() => {
+ expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(1);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledWith(
+ expect.objectContaining({
+ properties: expect.objectContaining({
+ selected_provider: EVENT_PROVIDERS.CONSENSYS,
+ transaction_amount_eth: '1',
+ }),
}),
- }),
- );
+ );
+ });
});
});
diff --git a/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.tsx b/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.tsx
index 447570beef98..11645ca71182 100644
--- a/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.tsx
+++ b/app/components/Views/confirmations/external/staking/info/staking-withdrawal/staking-withdrawal.tsx
@@ -5,7 +5,7 @@ import { EVENT_PROVIDERS } from '../../../../../../UI/Stake/constants/events';
import useClearConfirmationOnBackSwipe from '../../../../hooks/ui/useClearConfirmationOnBackSwipe';
import { useConfirmationMetricEvents } from '../../../../hooks/metrics/useConfirmationMetricEvents';
import useNavbar from '../../../../hooks/ui/useNavbar';
-import { useTokenValues } from '../../../../hooks/useTokenValues';
+import { useTokenAmount } from '../../../../hooks/useTokenAmount';
import InfoSection from '../../../../components/UI/info-row/info-section';
import StakingContractInteractionDetails from '../../components/staking-contract-interaction-details/staking-contract-interaction-details';
import TokenHero from '../../../../components/rows/transactions/token-hero';
@@ -19,16 +19,21 @@ const StakingWithdrawal = ({ route }: UnstakeConfirmationViewProps) => {
useClearConfirmationOnBackSwipe();
const { trackPageViewedEvent, setConfirmationMetric } =
- useConfirmationMetricEvents();
- const { tokenAmountDisplayValue } = useTokenValues({ amountWei });
+ useConfirmationMetricEvents();
+ const { amount } = useTokenAmount({ amountWei });
+
useEffect(() => {
+ if (amount === undefined) {
+ return;
+ }
+
setConfirmationMetric({
properties: {
selected_provider: EVENT_PROVIDERS.CONSENSYS,
- transaction_amount_eth: tokenAmountDisplayValue,
+ transaction_amount_eth: amount,
},
});
- }, [tokenAmountDisplayValue, setConfirmationMetric]);
+ }, [amount, setConfirmationMetric]);
useEffect(trackPageViewedEvent, [trackPageViewedEvent]);
diff --git a/app/components/Views/confirmations/hooks/gas/useFeeCalculations.test.ts b/app/components/Views/confirmations/hooks/gas/useFeeCalculations.test.ts
index 8f935f5ea08c..87cddae33da8 100644
--- a/app/components/Views/confirmations/hooks/gas/useFeeCalculations.test.ts
+++ b/app/components/Views/confirmations/hooks/gas/useFeeCalculations.test.ts
@@ -17,6 +17,11 @@ jest.mock('../../../../../core/Engine', () => ({
},
}));
+jest.mock('../../utils/token', () => ({
+ ...jest.requireActual('../../../../utils/token'),
+ fetchErc20Decimals: jest.fn().mockResolvedValue(18),
+}));
+
describe('useFeeCalculations', () => {
const transactionMeta =
stakingDepositConfirmationState.engine.backgroundState.TransactionController
diff --git a/app/components/Views/confirmations/hooks/transactions/useSupportsEIP1559.test.ts b/app/components/Views/confirmations/hooks/transactions/useSupportsEIP1559.test.ts
index d6d8c765c058..41cd6287907a 100644
--- a/app/components/Views/confirmations/hooks/transactions/useSupportsEIP1559.test.ts
+++ b/app/components/Views/confirmations/hooks/transactions/useSupportsEIP1559.test.ts
@@ -5,7 +5,7 @@ import { renderHookWithProvider } from '../../../../../util/test/renderWithProvi
import { stakingDepositConfirmationState } from '../../../../../util/test/confirm-data-helpers';
import { useSupportsEIP1559 } from './useSupportsEIP1559';
-describe('useEIP1559TxFees', () => {
+describe('useSupportsEIP1559', () => {
it('returns true for EIP1559 transaction', async () => {
const { result } = renderHookWithProvider(
() =>
diff --git a/app/components/Views/confirmations/hooks/useConfirmActions.test.ts b/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
index 79ec76c831d3..3c4f37c43028 100644
--- a/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
+++ b/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
@@ -25,6 +25,15 @@ jest.mock('../../../../core/Engine', () => ({
acceptPendingApproval: jest.fn(),
rejectPendingApproval: jest.fn(),
context: {
+ KeyringController: {
+ state: {
+ keyrings: [
+ {
+ accounts: ['0x0000000000000000000000000000000000000000'],
+ },
+ ],
+ },
+ },
TokenListController: {
fetchTokenList: jest.fn(),
},
diff --git a/app/components/Views/confirmations/hooks/useTokenAmount.test.ts b/app/components/Views/confirmations/hooks/useTokenAmount.test.ts
new file mode 100644
index 000000000000..0be542f18100
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/useTokenAmount.test.ts
@@ -0,0 +1,61 @@
+import { useTokenAmount } from './useTokenAmount';
+import { renderHookWithProvider } from '../../../../util/test/renderWithProvider';
+import {
+ stakingDepositConfirmationState,
+ transferConfirmationState,
+} from '../../../../util/test/confirm-data-helpers';
+import { waitFor } from '@testing-library/react-native';
+
+jest.mock('../../../../core/Engine', () => ({
+ context: {
+ TokenListController: {
+ fetchTokenList: jest.fn(),
+ },
+ },
+}));
+
+describe('useTokenAmount', () => {
+ describe('returns amount and fiat display values', () => {
+ it('for a transfer type transaction', async () => {
+ const { result } = renderHookWithProvider(() => useTokenAmount(), {
+ state: transferConfirmationState,
+ });
+
+ await waitFor(async () => {
+ expect(result.current).toEqual({
+ amount: '0.0001',
+ amountPrecise: '0.0001',
+ fiat: '$0.36',
+ });
+ });
+ });
+
+ it('for a staking deposit', async () => {
+ const { result } = renderHookWithProvider(() => useTokenAmount(), {
+ state: stakingDepositConfirmationState,
+ });
+
+ await waitFor(async () => {
+ expect(result.current).toEqual({
+ amount: '0.0001',
+ amountPrecise: '0.0001',
+ fiat: '$0.36',
+ });
+ });
+ });
+
+ it('for a staking deposit and with amountWei defined', async () => {
+ const { result } = renderHookWithProvider(() => useTokenAmount({ amountWei: '1000000000000000' }), {
+ state: stakingDepositConfirmationState,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ amount: '0.001',
+ amountPrecise: '0.001',
+ fiat: '$3.60',
+ });
+ });
+ });
+ });
+});
diff --git a/app/components/Views/confirmations/hooks/useTokenAmount.ts b/app/components/Views/confirmations/hooks/useTokenAmount.ts
new file mode 100644
index 000000000000..a5c8a6f4c6b7
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/useTokenAmount.ts
@@ -0,0 +1,104 @@
+import { BigNumber } from 'bignumber.js';
+import { useSelector } from 'react-redux';
+import { NetworkClientId } from '@metamask/network-controller';
+import { TransactionType } from '@metamask/transaction-controller';
+import { Hex } from '@metamask/utils';
+
+import I18n from '../../../../../locales/i18n';
+import { formatAmount, formatAmountMaxPrecision } from '../../../UI/SimulationDetails/formatAmount';
+import { RootState } from '../../../../reducers';
+import { selectConversionRateByChainId } from '../../../../selectors/currencyRateController';
+import { selectContractExchangeRatesByChainId } from '../../../../selectors/tokenRatesController';
+import { safeToChecksumAddress } from '../../../../util/address';
+import { toBigNumber } from '../../../../util/number';
+import { calcTokenAmount } from '../../../../util/transactions';
+import { useAsyncResult } from '../../../hooks/useAsyncResult';
+import useFiatFormatter from '../../../UI/SimulationDetails/FiatDisplay/useFiatFormatter';
+import { NATIVE_TOKEN_ADDRESS } from '../constants/tokens';
+import { ERC20_DEFAULT_DECIMALS, fetchErc20Decimals } from '../utils/token';
+import { parseStandardTokenTransactionData } from '../utils/transaction';
+import { useTransactionMetadataRequest } from './transactions/useTransactionMetadataRequest';
+
+interface TokenAmountProps {
+ /**
+ * Optional value in wei to display. If not provided, the amount from the transactionMetadata will be used.
+ */
+ amountWei?: string;
+}
+
+interface TokenAmount {
+ amountPrecise: string | undefined;
+ amount: string | undefined;
+ fiat: string | undefined;
+}
+
+const useTokenDecimals = (tokenAddress: Hex, networkClientId?: NetworkClientId) => useAsyncResult(
+ async () => await fetchErc20Decimals(tokenAddress, networkClientId),
+ [tokenAddress, networkClientId],
+);
+
+export const useTokenAmount = ({ amountWei }: TokenAmountProps = {}): TokenAmount => {
+ const fiatFormatter = useFiatFormatter();
+ const {
+ chainId,
+ networkClientId,
+ txParams,
+ type: transactionType,
+ } = useTransactionMetadataRequest() ?? {};
+
+ const contractExchangeRates = useSelector((state: RootState) =>
+ selectContractExchangeRatesByChainId(state, chainId as Hex),
+ );
+ const nativeConversionRate = new BigNumber(
+ useSelector((state: RootState) =>
+ selectConversionRateByChainId(state, chainId as Hex),
+ ) ?? 1,
+ );
+
+ const tokenAddress = safeToChecksumAddress(txParams?.to) || NATIVE_TOKEN_ADDRESS;
+ const { value: decimals, pending } = useTokenDecimals(tokenAddress, networkClientId);
+
+ if (pending) {
+ return {
+ amountPrecise: undefined,
+ amount: undefined,
+ fiat: undefined,
+ };
+ }
+
+ const transactionData = parseStandardTokenTransactionData(txParams?.data);
+ const value = amountWei ?
+ toBigNumber.dec(amountWei) :
+ transactionData?.args?._value || txParams?.value || '0';
+
+ const amount = calcTokenAmount(value ?? '0', Number(decimals ?? ERC20_DEFAULT_DECIMALS));
+
+ let fiat;
+
+ switch (transactionType) {
+ case TransactionType.simpleSend:
+ case TransactionType.stakingClaim:
+ case TransactionType.stakingDeposit:
+ case TransactionType.stakingUnstake: {
+ // Native
+ fiat = amount.times(nativeConversionRate);
+ break;
+ }
+ case TransactionType.contractInteraction:
+ case TransactionType.tokenMethodTransfer: {
+ // ERC20
+ const contractExchangeRate = contractExchangeRates?.[tokenAddress]?.price ?? 1;
+ fiat = amount.times(nativeConversionRate).times(contractExchangeRate);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return {
+ amountPrecise: formatAmountMaxPrecision(I18n.locale, amount),
+ amount: formatAmount(I18n.locale, amount),
+ fiat: fiat ? fiatFormatter(fiat) : undefined,
+ };
+};
diff --git a/app/components/Views/confirmations/hooks/useTokenAsset.test.ts b/app/components/Views/confirmations/hooks/useTokenAsset.test.ts
new file mode 100644
index 000000000000..08d6723b4c2f
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/useTokenAsset.test.ts
@@ -0,0 +1,47 @@
+import { strings } from '../../../../../locales/i18n';
+import { useTokenAsset } from './useTokenAsset';
+import { renderHookWithProvider } from '../../../../util/test/renderWithProvider';
+import { backgroundState } from '../../../../util/test/initial-root-state';
+import { stakingDepositConfirmationState } from '../../../../util/test/confirm-data-helpers';
+
+jest.mock('./transactions/useTransactionMetadataRequest', () => ({
+ useTransactionMetadataRequest: jest.fn().mockReturnValue({
+ chainId: '0x1',
+ txParams: {
+ to: '0x0000000000000000000000000000000000000000',
+ from: '0x0000000000000000000000000000000000000000',
+ },
+ }),
+}));
+
+describe('useTokenAsset', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('returns "unknown" token name and symbol when the asset symbol is not found', () => {
+ const { result } = renderHookWithProvider(useTokenAsset, {
+ state: {
+ engine: {
+ backgroundState: {
+ ...backgroundState,
+ },
+ },
+ },
+ });
+
+ expect(result.current.displayName).toEqual(strings('token.unknown'));
+ });
+
+ it('returns asset', () => {
+ const { result } = renderHookWithProvider(useTokenAsset, {
+ state: stakingDepositConfirmationState,
+ });
+
+ expect(result.current.asset).toMatchObject({
+ name: 'Ethereum',
+ symbol: 'ETH',
+ });
+ expect(result.current.displayName).toEqual('ETH');
+ });
+});
diff --git a/app/components/Views/confirmations/hooks/useTokenAsset.ts b/app/components/Views/confirmations/hooks/useTokenAsset.ts
new file mode 100644
index 000000000000..144170b2f5d1
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/useTokenAsset.ts
@@ -0,0 +1,61 @@
+import { useSelector } from 'react-redux';
+import { TransactionType } from '@metamask/transaction-controller';
+import { Hex } from '@metamask/utils';
+
+import { strings } from '../../../../../locales/i18n';
+import { TokenI } from '../../../UI/Tokens/types';
+import { RootState } from '../../../../reducers';
+import { makeSelectAssetByAddressAndChainId } from '../../../../selectors/multichain';
+import { safeToChecksumAddress } from '../../../../util/address';
+import { NATIVE_TOKEN_ADDRESS } from '../constants/tokens';
+import { useTransactionMetadataRequest } from './transactions/useTransactionMetadataRequest';
+
+const selectEvmAsset = makeSelectAssetByAddressAndChainId();
+
+export const useTokenAsset = () => {
+ const { chainId, type: transactionType, txParams } = useTransactionMetadataRequest() ?? {};
+
+ const tokenAddress = safeToChecksumAddress(txParams?.to)?.toLowerCase() || NATIVE_TOKEN_ADDRESS;
+
+ const evmAsset = useSelector((state: RootState) =>
+ selectEvmAsset(state, {
+ address: tokenAddress,
+ chainId: chainId as Hex,
+ }),
+ );
+
+ const nativeEvmAsset = useSelector((state: RootState) =>
+ selectEvmAsset(state, {
+ address: NATIVE_TOKEN_ADDRESS,
+ chainId: chainId as Hex,
+ }),
+ );
+
+ let asset = {} as TokenI;
+
+ switch (transactionType) {
+ case TransactionType.simpleSend:
+ case TransactionType.stakingClaim:
+ case TransactionType.stakingDeposit:
+ case TransactionType.stakingUnstake: {
+ // Native
+ asset = nativeEvmAsset ?? {} as TokenI;
+ break;
+ }
+ case TransactionType.contractInteraction:
+ case TransactionType.tokenMethodTransfer:
+ default: {
+ // ERC20
+ asset = evmAsset ?? {} as TokenI;
+ break;
+ }
+ }
+
+ const { name, symbol, ticker } = asset;
+ const displayName = ticker ?? symbol ?? name ?? strings('token.unknown');
+
+ return {
+ asset,
+ displayName,
+ };
+};
diff --git a/app/components/Views/confirmations/hooks/useTokenValues.test.ts b/app/components/Views/confirmations/hooks/useTokenValues.test.ts
deleted file mode 100644
index 7f1d594cc58e..000000000000
--- a/app/components/Views/confirmations/hooks/useTokenValues.test.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { useTokenValues } from './useTokenValues';
-import { renderHookWithProvider } from '../../../../util/test/renderWithProvider';
-import { stakingDepositConfirmationState } from '../../../../util/test/confirm-data-helpers';
-
-jest.mock('../../../../core/Engine', () => ({
- context: {
- TokenListController: {
- fetchTokenList: jest.fn(),
- },
- },
-}));
-
-describe('useTokenValues', () => {
- describe('staking deposit', () => {
- it('returns token and fiat values if from transaction metadata', () => {
- const { result } = renderHookWithProvider(() => useTokenValues({ amountWei: undefined }), {
- state: stakingDepositConfirmationState,
- });
-
- expect(result.current).toEqual({
- tokenAmountValue: '0.0001',
- tokenAmountDisplayValue: '0.0001',
- fiatDisplayValue: '$0.36',
- });
- });
-
- it('returns token and fiat values if amountWei is defined', () => {
- const { result } = renderHookWithProvider(() => useTokenValues({ amountWei: '1000000000000000' }), {
- state: stakingDepositConfirmationState,
- });
-
- expect(result.current).toEqual({
- tokenAmountValue: '0.001',
- tokenAmountDisplayValue: '0.001',
- fiatDisplayValue: '$3.60',
- });
- });
- });
-});
diff --git a/app/components/Views/confirmations/hooks/useTokenValues.ts b/app/components/Views/confirmations/hooks/useTokenValues.ts
deleted file mode 100644
index ca44fa96a6c8..000000000000
--- a/app/components/Views/confirmations/hooks/useTokenValues.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { useSelector } from 'react-redux';
-import { BigNumber } from 'bignumber.js';
-import { Hex } from '@metamask/utils';
-
-import { useTransactionMetadataRequest } from './transactions/useTransactionMetadataRequest';
-import { selectConversionRateByChainId } from '../../../../selectors/currencyRateController';
-import I18n from '../../../../../locales/i18n';
-import { formatAmount } from '../../../../components/UI/SimulationDetails/formatAmount';
-import { fromWei, hexToBN, toBigNumber } from '../../../../util/number';
-import useFiatFormatter from '../../../UI/SimulationDetails/FiatDisplay/useFiatFormatter';
-import { RootState } from '../../../../reducers';
-
-// TODO: This hook will be extended to calculate token and fiat information from transaction metadata on upcoming redesigned confirmations
-export const useTokenValues = ({ amountWei }: { amountWei?: string } = {}) => {
-
- const transactionMetadata = useTransactionMetadataRequest();
-
- let ethAmountInWei;
- if (amountWei) {
- ethAmountInWei = toBigNumber.dec(amountWei);
- } else {
- ethAmountInWei = hexToBN(transactionMetadata?.txParams?.value);
- }
-
- const ethAmountInBN = new BigNumber(fromWei(ethAmountInWei, 'ether'));
-
- const tokenAmountValue = ethAmountInBN.toFixed();
-
- const locale = I18n.locale;
- const tokenAmountDisplayValue = formatAmount(locale, ethAmountInBN);
-
- const fiatFormatter = useFiatFormatter();
- const nativeConversionRate = useSelector((state: RootState) =>
- selectConversionRateByChainId(state, transactionMetadata?.chainId as Hex),
- );
- const nativeConversionRateInBN = new BigNumber(nativeConversionRate || 1);
- const preciseFiatValue = ethAmountInBN.times(nativeConversionRateInBN);
- const fiatDisplayValue = preciseFiatValue && fiatFormatter(preciseFiatValue);
-
- return {
- tokenAmountValue,
- tokenAmountDisplayValue,
- fiatDisplayValue,
- };
-};
diff --git a/app/util/address/index.ts b/app/util/address/index.ts
index 0e03c044ab82..0ba375cc0ea4 100644
--- a/app/util/address/index.ts
+++ b/app/util/address/index.ts
@@ -443,7 +443,7 @@ export function resemblesAddress(address: string) {
return address && address.length === 2 + 20 * 2;
}
-export function safeToChecksumAddress(address: string) {
+export function safeToChecksumAddress(address?: string) {
if (!address) return undefined;
return toChecksumAddress(address) as Hex;
}
diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts
index da6b23bb7499..5c999a293bb3 100644
--- a/app/util/test/confirm-data-helpers.ts
+++ b/app/util/test/confirm-data-helpers.ts
@@ -519,6 +519,16 @@ const stakingConfirmationBaseState = {
engine: {
backgroundState: {
...backgroundState,
+ AccountsController: {
+ internalAccounts: {
+ accounts: {
+ '0x0000000000000000000000000000000000000000': {
+ address: '0x0000000000000000000000000000000000000000',
+ },
+ },
+ selectedAccount: '0x0000000000000000000000000000000000000000',
+ },
+ },
ApprovalController: {
pendingApprovals: {
'699ca2f0-e459-11ef-b6f6-d182277cf5e1': {
@@ -549,6 +559,24 @@ const stakingConfirmationBaseState = {
},
},
},
+ TokensController: {
+ allTokens: {
+ '0x1': {
+ '0x0000000000000000000000000000000000000000': [{
+ address: '0x0000000000000000000000000000000000000000',
+ aggregators: [],
+ balance: '0xde0b6b3a7640000',
+ chainId: '0x1',
+ decimals: 18,
+ isETH: true,
+ isNative: true,
+ name: 'Ethereum',
+ symbol: 'ETH',
+ ticker: 'ETH',
+ }]
+ }
+ }
+ },
TransactionController: {
transactions: [
{
@@ -618,16 +646,7 @@ const stakingConfirmationBaseState = {
},
},
networkConfigurationsByChainId: {
- '0x1': {
- nativeCurrency: 'ETH',
- rpcEndpoints: [
- {
- networkClientId: 'mainnet',
- url: 'https://mainnet.infura.io/v3/1234567890',
- },
- ],
- defaultRpcEndpointIndex: 0,
- },
+ ...backgroundState.NetworkController.networkConfigurationsByChainId,
'0xaa36a7': {
nativeCurrency: 'ETH',
rpcEndpoints: [
@@ -705,16 +724,6 @@ export const stakingWithdrawalConfirmationState = merge(
TransactionController: {
transactions: [{ type: TransactionType.stakingUnstake }],
} as unknown as TransactionControllerState,
- AccountsController: {
- internalAccounts: {
- accounts: {
- '0x0000000000000000000000000000000000000000': {
- address: '0x0000000000000000000000000000000000000000',
- },
- },
- selectedAccount: '0x0000000000000000000000000000000000000000',
- },
- },
},
},
},
@@ -729,16 +738,6 @@ export const stakingClaimConfirmationState = merge(
TransactionController: {
transactions: [{ type: TransactionType.stakingClaim }],
} as unknown as TransactionControllerState,
- AccountsController: {
- internalAccounts: {
- accounts: {
- '0x0000000000000000000000000000000000000000': {
- address: '0x0000000000000000000000000000000000000000',
- },
- },
- selectedAccount: '0x0000000000000000000000000000000000000000',
- },
- },
},
},
},
diff --git a/locales/languages/en.json b/locales/languages/en.json
index b3433af5e00c..6abeae024cd0 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -709,7 +709,8 @@
"circulating_supply": "Circulating supply",
"all_time_high": "All time high",
"all_time_low": "All time low",
- "fully_diluted": "Fully diluted"
+ "fully_diluted": "Fully diluted",
+ "unknown": "Unknown"
},
"collectible": {
"collectible_address": "Address",