Skip to content

feat: Implement network-row component for redesigned transfer transactions #15281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('Transfer', () => {
expect(mockUseClearConfirmationOnBackSwipe).toHaveBeenCalled();
expect(getByText('0xDc477...0c164')).toBeDefined();
expect(getByText('Network Fee')).toBeDefined();
expect(getByText('Network')).toBeDefined();
expect(getNavbar).toHaveBeenCalled();
expect(getNavbar).toHaveBeenCalledWith({
title: 'Review',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FromTo from '../../rows/transactions/from-to';
import GasFeesDetails from '../../rows/transactions/gas-fee-details';
import AdvancedDetailsRow from '../../rows/transactions/advanced-details-row/advanced-details-row';
import TokenHero from '../../rows/transactions/token-hero';
import NetworkRow from '../../rows/transactions/network-row';
import styleSheet from './transfer.styles';

const Transfer = () => {
Expand All @@ -28,6 +29,7 @@ const Transfer = () => {
<View>
<TokenHero />
<FromTo />
<NetworkRow />
<View style={styles.simulationsDetailsContainer}>
<SimulationDetails
transaction={transactionMetadata as TransactionMeta}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './network-row';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StyleSheet } from 'react-native';

const styleSheet = () =>
StyleSheet.create({
infoRowOverride: {
paddingBottom: 4,
},
networkRowContainer: {
flexDirection: 'row',
alignItems: 'center',
},
avatarNetwork: {
marginRight: 4,
},
});

export default styleSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import { merge } from 'lodash';
import { Hex } from '@metamask/utils';

import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
import { transferConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
import { MMM_ORIGIN } from '../../../../constants/confirmations';
import NetworkRow from './network-row';

jest.mock('../../../../hooks/metrics/useConfirmationMetricEvents');
jest.mock('../../../../../../../core/Engine', () => ({
context: {
GasFeeController: {
startPolling: jest.fn(),
stopPollingByPollingToken: jest.fn(),
},
NetworkController: {
getNetworkConfigurationByNetworkClientId: jest.fn(),
},
TokenListController: {
fetchTokenList: jest.fn(),
},
},
}));

const MOCK_DAPP_ORIGIN = 'https://exampledapp.com';

const mockNetworkImage = { uri: 'https://mocknetwork.com/image.png' };
jest.mock('../../../../../../../util/networks', () => ({
getNetworkImageSource: jest.fn(() => mockNetworkImage),
}));

const networkConfigurationMock = {
name: 'Test Network',
chainId: '0x1' as Hex,
};

// Mock state with transaction from MetaMask Mobile origin
const internalTransactionState = merge({}, transferConfirmationState, {
engine: {
backgroundState: {
TransactionController: {
transactions: [
{
txParams: {
from: '0xdc47789de4ceff0e8fe9d15d728af7f17550c164',
},
origin: MMM_ORIGIN,
},
],
},
NetworkController: {
networkConfigurationsByChainId: {
'0x1': networkConfigurationMock,
},
},
},
},
});

// Mock state with transaction from external dapp origin
const dappOriginTransactionState = merge({}, transferConfirmationState, {
engine: {
backgroundState: {
TransactionController: {
transactions: [
{
txParams: {
from: '0xdc47789de4ceff0e8fe9d15d728af7f17550c164',
},
origin: MOCK_DAPP_ORIGIN,
},
],
},
NetworkController: {
networkConfigurationsByChainId: {
'0x1': networkConfigurationMock,
},
},
},
},
});

describe('NetworkRow', () => {
it('displays the correct network name', async () => {
const { getByText } = renderWithProvider(<NetworkRow />, {
state: internalTransactionState,
});

expect(getByText('Test Network')).toBeDefined();
expect(getByText('Network')).toBeDefined();
});

it('displays origin info for dapp transactions', async () => {
const { getByText } = renderWithProvider(<NetworkRow />, {
state: dappOriginTransactionState,
});

expect(getByText('Test Network')).toBeDefined();
expect(getByText('Request from')).toBeDefined();
expect(getByText(MOCK_DAPP_ORIGIN)).toBeDefined();
});

it('does not display origin info for internal transactions', async () => {
const { getByText, queryByText } = renderWithProvider(<NetworkRow />, {
state: internalTransactionState,
});

expect(getByText('Test Network')).toBeDefined();
expect(queryByText('Request from')).toBeNull();
expect(queryByText(MMM_ORIGIN)).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { View } from 'react-native';
import { useSelector } from 'react-redux';
import { Hex } from '@metamask/utils';

import { useTransactionMetadataRequest } from '../../../../hooks/transactions/useTransactionMetadataRequest';
import { selectNetworkConfigurationByChainId } from '../../../../../../../selectors/networkController';
import Text, {
TextVariant,
} from '../../../../../../../component-library/components/Texts/Text';
import { useStyles } from '../../../../../../../component-library/hooks';
import { getNetworkImageSource } from '../../../../../../../util/networks';
import { strings } from '../../../../../../../../locales/i18n';
import { RootState } from '../../../../../../../reducers';
import InfoSection from '../../../UI/info-row/info-section';
import InfoRow from '../../../UI/info-row/info-row';
import { MMM_ORIGIN } from '../../../../constants/confirmations';
import styleSheet from './network-row.styles';
import AvatarNetwork from '../../../../../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork';
import { AvatarSize } from '../../../../../../../component-library/components/Avatars/Avatar/Avatar.types';

const NetworkRow = () => {
const { styles } = useStyles(styleSheet, {});
const transactionMetadata = useTransactionMetadataRequest();
const chainId = transactionMetadata?.chainId;
const origin = transactionMetadata?.origin;
const networkConfiguration = useSelector((state: RootState) =>
selectNetworkConfigurationByChainId(state, chainId),
);
const isDappOrigin = origin !== MMM_ORIGIN;
const networkImage = getNetworkImageSource({ chainId: chainId as Hex });

if (!transactionMetadata) {
return null;
}

return (
<InfoSection>
<InfoRow
label={strings('transactions.network')}
style={styles.infoRowOverride}
>
<View style={styles.networkRowContainer}>
{networkImage && (
<AvatarNetwork
size={AvatarSize.Xs}
imageSource={networkImage}
style={styles.avatarNetwork}
/>
)}
<Text variant={TextVariant.BodyMD}>{networkConfiguration?.name}</Text>
</View>
</InfoRow>

{isDappOrigin && (
<InfoRow
label={strings('transactions.request_from')}
style={styles.infoRowOverride}
>
<Text variant={TextVariant.BodyMD}>{origin}</Text>
</InfoRow>
)}
</InfoSection>
);
};

export default NetworkRow;
25 changes: 16 additions & 9 deletions app/components/Views/confirmations/utils/deeplink.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TransactionType } from '@metamask/transaction-controller';
import { ParseOutput } from 'eth-url-parser';

import { ETH_ACTIONS } from '../../../../constants/deeplinks';
import { selectConfirmationRedesignFlagsFromRemoteFeatureFlags } from '../../../../selectors/featureFlagController/confirmations';
Expand All @@ -9,8 +8,11 @@ import { generateTransferData } from '../../../../util/transactions';
import {
addTransactionForDeeplink,
isDeeplinkRedesignedConfirmationCompatible,
type DeeplinkRequest,
} from './deeplink';

const ORIGIN_MOCK = 'example.test-dapp.com';

jest.mock('../../../../core/Engine', () => ({
context: {
RemoteFeatureFlagController: {
Expand Down Expand Up @@ -142,7 +144,8 @@ describe('addTransactionForDeeplink', () => {
value: '1000',
},
target_address: TO_ADDRESS_MOCK,
} as unknown as ParseOutput);
origin: ORIGIN_MOCK,
} as unknown as DeeplinkRequest);

expect(mockAddTransaction).toHaveBeenCalledWith(
{
Expand All @@ -152,7 +155,7 @@ describe('addTransactionForDeeplink', () => {
},
{
networkClientId: 'another-network',
origin: 'deeplink',
origin: ORIGIN_MOCK,
type: TransactionType.simpleSend,
},
);
Expand All @@ -164,7 +167,8 @@ describe('addTransactionForDeeplink', () => {
value: '1000',
},
target_address: TO_ADDRESS_MOCK,
} as unknown as ParseOutput);
origin: ORIGIN_MOCK,
} as unknown as DeeplinkRequest);

expect(mockAddTransaction).toHaveBeenCalledWith(
{
Expand All @@ -174,7 +178,7 @@ describe('addTransactionForDeeplink', () => {
},
{
networkClientId: 'mainnet',
origin: 'deeplink',
origin: ORIGIN_MOCK,
type: TransactionType.simpleSend,
},
);
Expand All @@ -187,7 +191,8 @@ describe('addTransactionForDeeplink', () => {
value: '1000',
},
target_address: TO_ADDRESS_MOCK,
} as unknown as ParseOutput);
origin: ORIGIN_MOCK,
} as unknown as DeeplinkRequest);

expect(mockAddTransaction).toHaveBeenCalledTimes(1);

Expand All @@ -196,7 +201,8 @@ describe('addTransactionForDeeplink', () => {
value: '9999',
},
target_address: TO_ADDRESS_MOCK,
} as unknown as ParseOutput);
origin: ORIGIN_MOCK,
} as unknown as DeeplinkRequest);

expect(mockAddTransaction).toHaveBeenCalledTimes(1);
});
Expand All @@ -214,7 +220,8 @@ describe('addTransactionForDeeplink', () => {
uint256: '1000',
},
target_address: ERC20_ADDRESS_MOCK,
} as unknown as ParseOutput);
origin: ORIGIN_MOCK,
} as unknown as DeeplinkRequest);

expect(mockGenerateTransferData).toHaveBeenCalledWith(
'transfer',
Expand All @@ -232,7 +239,7 @@ describe('addTransactionForDeeplink', () => {
},
{
networkClientId: 'mainnet',
origin: 'deeplink',
origin: ORIGIN_MOCK,
type: TransactionType.tokenMethodTransfer,
},
);
Expand Down
11 changes: 7 additions & 4 deletions app/components/Views/confirmations/utils/deeplink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { ETH_ACTIONS } from '../../../../constants/deeplinks';
import Engine from '../../../../core/Engine';
import Logger from '../../../../util/Logger';

export type DeeplinkRequest = ParseOutput & { origin: string };

const getNetworkClientIdForChainId = (chainId: Hex) => {
const { NetworkController } = Engine.context;
const selectedNetworkClientId = getGlobalNetworkClientId();
Expand Down Expand Up @@ -60,7 +62,8 @@ export async function addTransactionForDeeplink({
function_name,
parameters,
target_address,
}: ParseOutput) {
origin,
}: DeeplinkRequest) {
const { AccountsController } = Engine.context;

// Temporary solution for preventing back to back deeplink requests
Expand All @@ -82,12 +85,12 @@ export async function addTransactionForDeeplink({
chainId = CHAIN_IDS.MAINNET;
}

// This should be anything *except* 'MMM' (MetaMask Mobile) to avoid layout issues in redesigned confirmations
const origin = 'deeplink';
const networkClientId = getNetworkClientIdForChainId(chainId);
const from = safeToChecksumAddress(selectedAccountAddress) as string;
const to = safeToChecksumAddress(target_address);
const checkSummedParamAddress = safeToChecksumAddress(parameters?.address ?? '');
const checkSummedParamAddress = safeToChecksumAddress(
parameters?.address ?? '',
);

if (function_name === ETH_ACTIONS.TRANSFER) {
// ERC20 transfer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ describe('handleEthereumUrl', () => {
function_name: ETH_ACTIONS.TRANSFER,
chain_id: 1,
source: url,
origin,
});
});

Expand Down
5 changes: 4 additions & 1 deletion app/core/DeeplinkManager/Handlers/handleEthereumUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ async function handleEthereumUrl({
}

if (isDeeplinkRedesignedConfirmationCompatible(ethUrl.function_name)) {
await addTransactionForDeeplink(txMeta);
await addTransactionForDeeplink({
...txMeta,
origin,
});
return;
}

Expand Down
2 changes: 2 additions & 0 deletions locales/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,8 @@
"sign_description_2": "tap on Get Signature",
"sign_get_signature": "Get Signature",
"transaction_id": "Transaction ID",
"network": "Network",
"request_from": "Request from",
"network_fee": "Network Fee",
"network_fee_tooltip": "Amount paid to process the transaction on network."
},
Expand Down
Loading