From 226107b8114721b5ca62630c48f11764636ace2a Mon Sep 17 00:00:00 2001 From: Vince Howard Date: Tue, 10 Dec 2024 19:15:47 -0700 Subject: [PATCH 1/3] fix: hide tokens without balance for multichain (#12630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fixed an issue where multi chain balance was not being calculated correctly for hide zero tokens settings. Also fixed an issue where the ticker was `undefined` in assets overview when it was a non-native token. These fixes belong to a feature that is hidden behind a feature flag `PORTFOLIO_VIEW` ## **Related issues** Fixes: ## **Manual testing steps** 1. Goto settings and turn on "Hide Tokens Without Balance" 2. Observe that your zero tokens and tokens with no conversion rate disappear ## **Screenshots/Recordings** ### Zero Balance | Before | After | |:---:|:---:| |![zero_balance_before](https://github.com/user-attachments/assets/21bde196-951f-447e-9de0-ce214cee4a1f)|![zero_balance_after](https://github.com/user-attachments/assets/a92aaa8f-5c08-4c45-bd0d-0fdaff89ca74)| ### Ticker | Before | After | |:---:|:---:| |![ticker_before](https://github.com/user-attachments/assets/15776130-ea3e-4cc9-901d-61d591dbaab9)|![ticker_after](https://github.com/user-attachments/assets/b5384a6d-47da-4c92-9b63-2709de52c66d)| ### **Before** NA ### **After** NA ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/components/UI/AssetOverview/AssetOverview.tsx | 2 +- .../__snapshots__/AssetOverview.test.tsx.snap | 2 +- app/components/UI/Tokens/index.tsx | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index dcb345b1904..cbcd738dca6 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -383,7 +383,7 @@ const AssetOverview: React.FC = ({ : `${balance} ${asset.symbol}`; } } else { - mainBalance = `${balance} ${asset.ticker}`; + mainBalance = `${balance} ${asset.isETH ? asset.ticker : asset.symbol}`; secondaryBalance = exchangeRate ? asset.balanceFiat : ''; } diff --git a/app/components/UI/AssetOverview/__snapshots__/AssetOverview.test.tsx.snap b/app/components/UI/AssetOverview/__snapshots__/AssetOverview.test.tsx.snap index 4ec605677fb..c1eba443d22 100644 --- a/app/components/UI/AssetOverview/__snapshots__/AssetOverview.test.tsx.snap +++ b/app/components/UI/AssetOverview/__snapshots__/AssetOverview.test.tsx.snap @@ -2253,7 +2253,7 @@ exports[`AssetOverview should render correctly when portfolio view is enabled 1` } testID="main-balance-test-id" > - 0 undefined + 0 ETH diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index b4d895b45f9..11d8401521c 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -146,12 +146,14 @@ const Tokens: React.FC = ({ tokens }) => { // First filter zero balance tokens if setting is enabled const tokensToDisplay = hideZeroBalanceTokens - ? allTokens.filter( - (curToken) => - !isZero(curToken.balance) || - curToken.isNative || - curToken.isStaked, - ) + ? allTokens.filter((curToken) => { + const multiChainTokenBalances = + multiChainTokenBalance?.[selectedInternalAccountAddress as Hex]?.[ + curToken.chainId as Hex + ]; + const balance = multiChainTokenBalances?.[curToken.address as Hex]; + return !isZero(balance) || curToken.isNative || curToken.isStaked; + }) : allTokens; // Then apply network filters From 259faa356165b318b35faf8c9d12ad5077b6fe0a Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Wed, 11 Dec 2024 03:32:05 +0100 Subject: [PATCH 2/3] feat: activate portfolio view (#12507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR turns on the Portfolio view feature flag. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: vinnyhoward Co-authored-by: sahar-fehri Co-authored-by: Nicholas Gambino Co-authored-by: Nick Gambino <35090461+gambinish@users.noreply.github.com> --- .js.env.example | 14 +- .../AccountSelector.test.tsx | 13 + .../StakingBalance.test.tsx.snap | 472 +++++++++++++++++- bitrise.yml | 2 +- jest.config.js | 1 + 5 files changed, 495 insertions(+), 7 deletions(-) diff --git a/.js.env.example b/.js.env.example index 1c11f591536..f03063c77fe 100644 --- a/.js.env.example +++ b/.js.env.example @@ -1,10 +1,10 @@ # Sign up and generate your own keys at pubnub.com # Then rename this file to ".js.env" and rebuild the app -# +# # In order for this feature to work properly, you need to # build metamask-extension from source (https://github.com/MetaMask/metamask-extension) # and set your the same values there. -# +# # For more info take a look at https://github.com/MetaMask/metamask-extension/pull/5955 export MM_PUBNUB_SUB_KEY="" @@ -70,6 +70,10 @@ export SEGMENT_FLUSH_EVENT_LIMIT="1" # URL of security alerts API used to validate dApp requests. export SECURITY_ALERTS_API_URL="https://security-alerts.api.cx.metamask.io" +# Enable Portfolio View +export PORTFOLIO_VIEW="true" + + # Temporary mechanism to enable security alerts API prior to release. export MM_SECURITY_ALERTS_API_ENABLED="true" # Firebase @@ -81,7 +85,7 @@ export FCM_CONFIG_MESSAGING_SENDER_ID="" export FCM_CONFIG_APP_ID="" export GOOGLE_SERVICES_B64_ANDROID="" export GOOGLE_SERVICES_B64_IOS="" -#Notifications Feature Announcements +# Notifications Feature Announcements export FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN= export FEATURES_ANNOUNCEMENTS_SPACE_ID= @@ -96,8 +100,8 @@ export MM_PER_DAPP_SELECTED_NETWORK="" export MM_CHAIN_PERMISSIONS="" -#Multichain feature flag specific to UI changes +# Multichain feature flag specific to UI changes export MM_MULTICHAIN_V1_ENABLED="" -#Permissions Settings feature flag specific to UI changes +# Permissions Settings feature flag specific to UI changes export MM_PERMISSIONS_SETTINGS_V1_ENABLED="" diff --git a/app/components/UI/AccountSelectorList/AccountSelector.test.tsx b/app/components/UI/AccountSelectorList/AccountSelector.test.tsx index ec3a6c860fd..ad88315241f 100644 --- a/app/components/UI/AccountSelectorList/AccountSelector.test.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelector.test.tsx @@ -18,6 +18,9 @@ import { mockNetworkState } from '../../../util/test/network'; import { CHAIN_IDS } from '@metamask/transaction-controller'; import { AccountSelectorListProps } from './AccountSelectorList.types'; +// eslint-disable-next-line import/no-namespace +import * as Utils from '../../hooks/useAccounts/utils'; + const BUSINESS_ACCOUNT = '0xC4955C0d639D99699Bfd7Ec54d9FaFEe40e4D272'; const PERSONAL_ACCOUNT = '0xd018538C87232FF95acbCe4870629b75640a78E7'; @@ -125,6 +128,16 @@ const renderComponent = ( describe('AccountSelectorList', () => { beforeEach(() => { + jest.spyOn(Utils, 'getAccountBalances').mockReturnValueOnce({ + balanceETH: '1', + balanceFiat: '$3200.00', + balanceWeiHex: '', + }); + jest.spyOn(Utils, 'getAccountBalances').mockReturnValueOnce({ + balanceETH: '2', + balanceFiat: '$6400.00', + balanceWeiHex: '', + }); onSelectAccount.mockClear(); onRemoveImportedAccount.mockClear(); }); diff --git a/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap b/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap index 8095e5ad753..61fe94e4e3f 100644 --- a/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap +++ b/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap @@ -158,7 +158,477 @@ exports[`StakingBalance render matches snapshot 1`] = ` resizeMode="contain" source={ { - "uri": "MockImage", + "default": { + "uri": "MockImage", + }, + } + } + style={ + { + "height": 32, + "width": 32, + } + } + testID="network-avatar-image" + /> + + + + + + Staked Ethereum + + + + + + + + + + + Unstaking 0.0010 ETH in progress. Come back in a few days to claim it. + + + + + + + + + + You can claim 0.00214 ETH. Once claimed, you'll get ETH back in your wallet. + + + + Claim + ETH + + + + + + + + Unstake + + + + + Stake more + + + + + +`; + +exports[`StakingBalance should match the snapshot when portfolio view is enabled 1`] = ` + + + + + + + + + + + + + + + Date: Wed, 11 Dec 2024 15:15:16 +0000 Subject: [PATCH 3/3] feat: upgrade transaction controller to get incoming transactions using accounts API (#12419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Update `@metamask/transaction-controller` to retrieve incoming transactions using the accounts API rather than Etherscan. Add incoming transaction E2E tests. ## **Related issues** ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .js.env.example | 1 - app/components/Nav/Main/index.js | 6 +- app/components/UI/NetworkCell/NetworkCell.tsx | 10 +- .../UI/Notification/BaseNotification/index.js | 2 +- app/components/UI/Transactions/index.js | 10 +- app/components/Views/Asset/index.js | 8 +- .../Views/NetworkSelector/NetworkSelector.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 606 ------------------ .../__snapshots__/index.test.tsx.snap | 34 - .../IncomingTransactionsSettings/index.tsx | 24 +- .../NetworksSettings/NetworkSettings/index.js | 62 +- .../Views/Settings/NetworksSettings/index.js | 5 +- .../SecuritySettings.test.tsx.snap | 606 ------------------ .../Views/TransactionsView/index.js | 2 +- app/core/Engine/Engine.ts | 12 +- app/core/NotificationManager.js | 87 +-- e2e/fixtures/fixture-builder.js | 82 ++- e2e/pages/Transactions/ActivitiesView.js | 12 +- .../Transactions/ActivitiesView.selectors.js | 4 + .../wallet/incoming-transactions.spec.js | 202 ++++++ package.json | 2 +- yarn.lock | 8 +- 22 files changed, 391 insertions(+), 1396 deletions(-) create mode 100644 e2e/specs/wallet/incoming-transactions.spec.js diff --git a/.js.env.example b/.js.env.example index f03063c77fe..56c7c1bb865 100644 --- a/.js.env.example +++ b/.js.env.example @@ -10,7 +10,6 @@ export MM_PUBNUB_SUB_KEY="" export MM_PUBNUB_PUB_KEY="" export MM_OPENSEA_KEY="" -export MM_ETHERSCAN_KEY="" export MM_FOX_CODE="EXAMPLE_FOX_CODE" # NOTE: Non-MetaMask only, will need to create an account and generate diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 5330bfdb1ea..b8ba760da0c 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -133,7 +133,7 @@ const Main = (props) => { stopIncomingTransactionPolling(); if (showIncomingTransactionsNetworks[chainId]) { - startIncomingTransactionPolling([networkClientId]); + startIncomingTransactionPolling([chainId]); } }, [chainId, networkClientId, showIncomingTransactionsNetworks]); @@ -178,11 +178,11 @@ const Main = (props) => { removeNotVisibleNotifications(); BackgroundTimer.runBackgroundTimer(async () => { - await updateIncomingTransactions([props.networkClientId]); + await updateIncomingTransactions([props.chainId]); }, AppConstants.TX_CHECK_BACKGROUND_FREQUENCY); } }, - [backgroundMode, removeNotVisibleNotifications, props.networkClientId], + [backgroundMode, removeNotVisibleNotifications, props.chainId], ); const initForceReload = () => { diff --git a/app/components/UI/NetworkCell/NetworkCell.tsx b/app/components/UI/NetworkCell/NetworkCell.tsx index 58ea3d26c5d..a9816e6c86a 100644 --- a/app/components/UI/NetworkCell/NetworkCell.tsx +++ b/app/components/UI/NetworkCell/NetworkCell.tsx @@ -1,23 +1,21 @@ import React from 'react'; import { Switch, ImageSourcePropType } from 'react-native'; -import { ETHERSCAN_SUPPORTED_NETWORKS } from '@metamask/transaction-controller'; import { useStyles } from '../../../component-library/hooks'; import Cell from '../../../component-library/components/Cells/Cell/Cell'; import { CellVariant } from '../../../component-library/components/Cells/Cell'; import { AvatarVariant } from '../../../component-library/components/Avatars/Avatar/Avatar.types'; import { useTheme } from '../../../util/theme'; -import { EtherscanSupportedHexChainId } from '@metamask/preferences-controller'; import styleSheet from './NetworkCell.styles'; +import { Hex } from '@metamask/utils'; -const supportedNetworks = ETHERSCAN_SUPPORTED_NETWORKS; interface NetworkCellProps { name: string; - chainId: EtherscanSupportedHexChainId | keyof typeof supportedNetworks; + chainId: Hex; imageSource: ImageSourcePropType; - secondaryText: string; + secondaryText?: string; showIncomingTransactionsNetworks: Record; toggleEnableIncomingTransactions: ( - chainId: EtherscanSupportedHexChainId, + chainId: Hex, value: boolean, ) => void; testID?: string; diff --git a/app/components/UI/Notification/BaseNotification/index.js b/app/components/UI/Notification/BaseNotification/index.js index a8fe45ea886..8c137a39168 100644 --- a/app/components/UI/Notification/BaseNotification/index.js +++ b/app/components/UI/Notification/BaseNotification/index.js @@ -154,7 +154,7 @@ const getTitle = (status, { nonce, amount, assetType }) => { }; export const getDescription = (status, { amount = null, type = null }) => { - if (amount && typeof amount !== 'object') { + if (amount && typeof amount !== 'object' && type) { return strings(`notifications.${type}_${status}_message`, { amount }); } return strings(`notifications.${status}_message`); diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js index 3cc18c5725a..ac5d5155957 100644 --- a/app/components/UI/Transactions/index.js +++ b/app/components/UI/Transactions/index.js @@ -76,6 +76,7 @@ import { } from '../../../util/transaction-controller'; import { selectGasFeeEstimates } from '../../../selectors/confirmTransaction'; import { decGWEIToHexWEI } from '../../../util/conversions'; +import { ActivitiesViewSelectorsIDs } from '../../../../e2e/selectors/Transactions/ActivitiesView.selectors'; const createStyles = (colors, typography) => StyleSheet.create({ @@ -213,10 +214,6 @@ class Transactions extends PureComponent { */ onScrollThroughContent: PropTypes.func, gasFeeEstimates: PropTypes.object, - /** - * ID of the global network client - */ - networkClientId: PropTypes.string, }; static defaultProps = { @@ -352,11 +349,11 @@ class Transactions extends PureComponent { }; onRefresh = async () => { - const { networkClientId } = this.props; + const { chainId } = this.props; this.setState({ refreshing: true }); - await updateIncomingTransactions([networkClientId]); + await updateIncomingTransactions([chainId]); this.setState({ refreshing: false }); }; @@ -791,6 +788,7 @@ class Transactions extends PureComponent { {({ isChartBeingTouched }) => ( { - const { networkClientId } = this.props; + const { chainId } = this.props; this.setState({ refreshing: true }); - await updateIncomingTransactions([networkClientId]); + await updateIncomingTransactions([chainId]); this.setState({ refreshing: false }); }; diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index 444daa341b1..a0db393144f 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -399,7 +399,7 @@ const NetworkSelector = () => { AccountTrackerController.refresh(); setTimeout(async () => { - await updateIncomingTransactions([clientId]); + await updateIncomingTransactions([networkConfiguration.chainId]); }, 1000); } diff --git a/app/components/Views/OnboardingSuccess/OnboardingAssetsSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/OnboardingSuccess/OnboardingAssetsSettings/__snapshots__/index.test.tsx.snap index b37f7966a1a..57456e17de5 100644 --- a/app/components/Views/OnboardingSuccess/OnboardingAssetsSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/OnboardingSuccess/OnboardingAssetsSettings/__snapshots__/index.test.tsx.snap @@ -459,7 +459,6 @@ exports[`OnboardingAssetSettings should render correctly 1`] = ` "variant": "Network", } } - secondaryText="etherscan.io" style={ { "backgroundColor": "#ffffff", @@ -538,22 +537,6 @@ exports[`OnboardingAssetSettings should render correctly 1`] = ` > Ethereum Mainnet - - etherscan.io - Linea - - lineascan.build - - - - - - G - - - - - Goerli - - - etherscan.io - - - - - - - - - - - - - - - Sepolia - - - etherscan.io - - - - - - - - - - - - L - - - - - Linea Goerli - - - lineascan.build - - - - - - - - - - - - - - - Linea Sepolia - - - lineascan.build - - - - - - - Mainnet - - etherscan.io - Linea Mainnet - - lineascan.build - { const { styles } = useStyles(styleSheet, {}); @@ -40,14 +41,12 @@ const IncomingTransactionsSettings = () => { const networkConfigurations = useSelector(selectNetworkConfigurations); - const supportedNetworks = ETHERSCAN_SUPPORTED_NETWORKS; - const toggleEnableIncomingTransactions = ( - hexChainId: EtherscanSupportedHexChainId, + hexChainId: Hex, value: boolean, ) => { PreferencesController.setEnableNetworkIncomingTransactions( - hexChainId, + hexChainId as EtherscanSupportedHexChainId, value, ); }; @@ -73,7 +72,10 @@ const IncomingTransactionsSettings = () => { chainId, defaultRpcEndpointIndex, }: NetworkConfiguration) => { - if (!chainId || !Object.keys(supportedNetworks).includes(chainId)) + if ( + !chainId || + !INCOMING_TRANSACTIONS_SUPPORTED_CHAIN_IDS.includes(chainId) + ) return null; const rpcUrl = rpcEndpoints[defaultRpcEndpointIndex].url; @@ -88,8 +90,6 @@ const IncomingTransactionsSettings = () => { //@ts-expect-error - The utils/network file is still JS and this function expects a networkType, and should be optional const image = getNetworkImageSource({ chainId: chainId?.toString() }); - const secondaryText = - supportedNetworks[chainId as keyof typeof supportedNetworks].domain; return ( { name={name} chainId={chainId as EtherscanSupportedHexChainId} imageSource={image} - secondaryText={secondaryText} showIncomingTransactionsNetworks={showIncomingTransactionsNetworks} toggleEnableIncomingTransactions={toggleEnableIncomingTransactions} testID={testId} @@ -113,16 +112,15 @@ const IncomingTransactionsSettings = () => { const getOtherNetworks = () => getAllNetworks().slice(2); return getOtherNetworks().map((networkType) => { const { name, imageSource, chainId } = NetworksTyped[networkType]; + if (!chainId) return null; - const secondaryText = - supportedNetworks[chainId as keyof typeof supportedNetworks].domain; + return ( diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js index 21c7659023a..f937c9130ac 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js @@ -541,7 +541,7 @@ export class NetworkSettings extends PureComponent { editable = false; blockExplorerUrl = networkConfigurations?.[chainId]?.blockExplorerUrls[ - networkConfigurations?.[chainId]?.defaultBlockExplorerUrlIndex + networkConfigurations?.[chainId]?.defaultBlockExplorerUrlIndex ]; rpcUrl = networkConfigurations?.[chainId]?.rpcEndpoints[ @@ -563,13 +563,13 @@ export class NetworkSettings extends PureComponent { ({ rpcEndpoints, defaultRpcEndpointIndex }) => rpcEndpoints[defaultRpcEndpointIndex].url === networkTypeOrRpcUrl || rpcEndpoints[defaultRpcEndpointIndex].networkClientId === - networkTypeOrRpcUrl, + networkTypeOrRpcUrl, ); nickname = networkConfiguration?.name; chainId = networkConfiguration?.chainId; blockExplorerUrl = networkConfiguration?.blockExplorerUrls[ - networkConfiguration?.defaultBlockExplorerUrlIndex + networkConfiguration?.defaultBlockExplorerUrlIndex ]; ticker = networkConfiguration?.nativeCurrency; editable = true; @@ -854,8 +854,8 @@ export class NetworkSettings extends PureComponent { networkConfig, existingNetwork.chainId === chainId ? { - replacementSelectedRpcEndpointIndex: indexRpc, - } + replacementSelectedRpcEndpointIndex: indexRpc, + } : undefined, ); } else { @@ -867,8 +867,8 @@ export class NetworkSettings extends PureComponent { isCustomMainnet ? navigation.navigate('OptinMetrics') : shouldNetworkSwitchPopToWallet - ? navigation.navigate('WalletView') - : navigation.goBack(); + ? navigation.navigate('WalletView') + : navigation.goBack(); }; /** @@ -1534,13 +1534,13 @@ export class NetworkSettings extends PureComponent { const { networkClientId } = networkConfigurations?.rpcEndpoints?.[ - networkConfigurations.defaultRpcEndpointIndex + networkConfigurations.defaultRpcEndpointIndex ] ?? {}; NetworkController.setActiveNetwork(networkClientId); setTimeout(async () => { - await updateIncomingTransactions([networkClientId]); + await updateIncomingTransactions([CHAIN_IDS.MAINNET]); }, 1000); }; @@ -1950,15 +1950,15 @@ export class NetworkSettings extends PureComponent { // Conditionally include secondaryText only if rpcName exists {...(rpcName ? { - secondaryText: - hideKeyFromUrl(rpcUrl) ?? - hideKeyFromUrl( - networkConfigurations?.[chainId]?.rpcEndpoints?.[ - networkConfigurations?.[chainId] - ?.defaultRpcEndpointIndex - ]?.url, - ), - } + secondaryText: + hideKeyFromUrl(rpcUrl) ?? + hideKeyFromUrl( + networkConfigurations?.[chainId]?.rpcEndpoints?.[ + networkConfigurations?.[chainId] + ?.defaultRpcEndpointIndex + ]?.url, + ), + } : {})} isSelected={false} withAvatar={false} @@ -1993,17 +1993,17 @@ export class NetworkSettings extends PureComponent { {!isNetworkUiRedesignEnabled() ? warningRpcUrl && ( - - {warningRpcUrl} - - ) + + {warningRpcUrl} + + ) : null} @@ -2268,7 +2268,7 @@ export class NetworkSettings extends PureComponent { ) : null} {isNetworkUiRedesignEnabled() && - showMultiBlockExplorerAddModal.isVisible ? ( + showMultiBlockExplorerAddModal.isVisible ? ( 0 ? styles.sheet : styles.sheetSmall @@ -2478,7 +2478,7 @@ export class NetworkSettings extends PureComponent { > {(isNetworkUiRedesignEnabled() && !shouldShowPopularNetworks) || - networkTypeOrRpcUrl ? ( + networkTypeOrRpcUrl ? ( this.customNetwork() ) : ( StyleSheet.create({ @@ -191,7 +192,7 @@ class NetworksSettings extends PureComponent { NetworkController.setProviderType(MAINNET); setTimeout(async () => { - await updateIncomingTransactions([MAINNET]); + await updateIncomingTransactions([CHAIN_IDS.MAINNET]); }, 1000); }; @@ -451,7 +452,7 @@ class NetworksSettings extends PureComponent { (networkConfiguration, i) => { const defaultRpcEndpoint = networkConfiguration.rpcEndpoints[ - networkConfiguration.defaultRpcEndpointIndex + networkConfiguration.defaultRpcEndpointIndex ]; const { color, name, url, chainId } = { name: networkConfiguration.name || defaultRpcEndpoint.url, diff --git a/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap b/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap index 8f218fb0c8b..8cd3a457694 100644 --- a/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap +++ b/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap @@ -1563,7 +1563,6 @@ exports[`SecuritySettings should render correctly 1`] = ` "variant": "Network", } } - secondaryText="etherscan.io" style={ { "backgroundColor": "#ffffff", @@ -1642,22 +1641,6 @@ exports[`SecuritySettings should render correctly 1`] = ` > Ethereum Mainnet - - etherscan.io - Linea - - lineascan.build - - - - - - G - - - - - Goerli - - - etherscan.io - - - - - - - - - - - - - - - Sepolia - - - etherscan.io - - - - - - - - - - - - L - - - - - Linea Goerli - - - lineascan.build - - - - - - - - - - - - - - - Linea Sepolia - - - lineascan.build - - - - - - - { - NotificationManager.gotIncomingTransaction(blockNumber); + 'TransactionController:incomingTransactionsReceived', + (incomingTransactions: TransactionMeta[]) => { + NotificationManager.gotIncomingTransaction(incomingTransactions); }, ); @@ -1666,10 +1666,12 @@ export class Engine { startPolling() { const { NetworkController, TransactionController } = this.context; - const networkClientId = getGlobalNetworkClientId(NetworkController); + const chainId = getGlobalChainId(NetworkController); + + TransactionController.stopIncomingTransactionPolling(); // leaving the reference of TransactionController here, rather than importing it from utils to avoid circular dependency - TransactionController.startIncomingTransactionPolling([networkClientId]); + TransactionController.startIncomingTransactionPolling([chainId]); } configureControllersOnNetworkChange() { diff --git a/app/core/NotificationManager.js b/app/core/NotificationManager.js index 013a49ebce9..9f068dbac6f 100644 --- a/app/core/NotificationManager.js +++ b/app/core/NotificationManager.js @@ -9,13 +9,14 @@ import NotificationsService from '../util/notifications/services/NotificationSer import { NotificationTransactionTypes, ChannelId } from '../util/notifications'; import { safeToChecksumAddress, formatAddress } from '../util/address'; import ReviewManager from './ReviewManager'; -import { selectChainId, selectTicker } from '../selectors/networkController'; +import { selectTicker } from '../selectors/networkController'; import { store } from '../store'; -import { useSelector } from 'react-redux'; import { getTicker } from '../../app/util/transactions'; import { updateTransaction } from '../../app/util/transaction-controller'; import { SmartTransactionStatuses } from '@metamask/smart-transactions-controller/dist/types'; +import Logger from '../util/Logger'; +import { TransactionStatus } from '@metamask/transaction-controller'; export const constructTitleAndMessage = (notification) => { let title, message; switch (notification.type) { @@ -431,56 +432,60 @@ class NotificationManager { /** * Generates a notification for an incoming transaction */ - gotIncomingTransaction = async (lastBlock) => { - const { - AccountTrackerController, - TransactionController, - AccountsController, - } = Engine.context; - const selectedInternalAccount = AccountsController.getSelectedAccount(); - const selectedInternalAccountChecksummedAddress = safeToChecksumAddress( - selectedInternalAccount.address, - ); + gotIncomingTransaction = async (incomingTransactions) => { + try { + const { + AccountTrackerController, + AccountsController, + } = Engine.context; + + const selectedInternalAccount = AccountsController.getSelectedAccount(); - const chainId = selectChainId(store.getState()); - const ticker = useSelector(selectTicker); + const selectedInternalAccountChecksummedAddress = safeToChecksumAddress( + selectedInternalAccount.address, + ); - /// Find the incoming TX - const transactions = TransactionController.getTransactions(); + const ticker = selectTicker(store.getState()); - // If a TX has been confirmed more than 10 min ago, it's considered old - const oldestTimeAllowed = Date.now() - 1000 * 60 * 10; + // If a TX has been confirmed more than 10 min ago, it's considered old + const oldestTimeAllowed = Date.now() - 1000 * 60 * 10; - if (transactions.length) { - const txs = transactions - .reverse() + const filteredTransactions = incomingTransactions.reverse() .filter( (tx) => safeToChecksumAddress(tx.txParams?.to) === selectedInternalAccountChecksummedAddress && safeToChecksumAddress(tx.txParams?.from) !== - selectedInternalAccountChecksummedAddress && - tx.chainId === chainId && - tx.status === 'confirmed' && - lastBlock <= parseInt(tx.blockNumber, 10) && + selectedInternalAccountChecksummedAddress && + tx.status === TransactionStatus.confirmed && tx.time > oldestTimeAllowed, ); - if (txs.length > 0) { - this._showNotification({ - type: 'received', - transaction: { - nonce: `${hexToBN(txs[0].txParams.nonce).toString()}`, - amount: `${renderFromWei(hexToBN(txs[0].txParams.value))}`, - id: txs[0]?.id, - assetType: getTicker(ticker), - }, - autoHide: true, - duration: 7000, - }); + + if (!filteredTransactions.length) { + return; } + + const nonce = hexToBN(filteredTransactions[0].txParams.nonce).toString(); + const amount = renderFromWei(hexToBN(filteredTransactions[0].txParams.value)); + const id = filteredTransactions[0]?.id; + + this._showNotification({ + type: 'received', + transaction: { + nonce, + amount, + id, + assetType: getTicker(ticker), + }, + autoHide: true, + duration: 7000, + }); + + // Update balance upon detecting a new incoming transaction + AccountTrackerController.refresh(); + } catch (error) { + Logger.log('Notifications', 'Error while processing incoming transaction', error); } - // Update balance upon detecting a new incoming transaction - AccountTrackerController.refresh(); }; } @@ -512,8 +517,8 @@ export default { setTransactionToView(id) { return instance?.setTransactionToView(id); }, - gotIncomingTransaction(lastBlock) { - return instance?.gotIncomingTransaction(lastBlock); + gotIncomingTransaction(incomingTransactions) { + return instance?.gotIncomingTransaction(incomingTransactions); }, showSimpleNotification(data) { return instance?.showSimpleNotification(data); diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index 383fa1d01d5..024f90a6c1a 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -3,6 +3,10 @@ import { getGanachePort } from './utils'; import { merge } from 'lodash'; import { CustomNetworks, PopularNetworksList } from '../resources/networks.e2e'; +import { CHAIN_IDS } from '@metamask/transaction-controller'; + +export const DEFAULT_FIXTURE_ACCOUNT = '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3'; + const DAPP_URL = 'localhost'; /** @@ -59,18 +63,18 @@ class FixtureBuilder { backgroundState: { AccountTrackerController: { accounts: { - '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3': { + [DEFAULT_FIXTURE_ACCOUNT]: { balance: '0x0', }, }, accountsByChainId: { 64: { - '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3': { + [DEFAULT_FIXTURE_ACCOUNT]: { balance: '0x0', }, }, 1: { - '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3': { + [DEFAULT_FIXTURE_ACCOUNT]: { balance: '0x0', }, }, @@ -134,7 +138,7 @@ class FixtureBuilder { '{"cipher":"ynNI8tAH4fcpmXo8S88A/3T3Dd1w0LY5ftpL59gW0ObYxovgFhrtKpRe/WD7WU42KwGBNKVicB9W9at4ePgOJGS6IMWr//C3jh0vKQTabkDzDy1ZfSvztRxGpVjmrnU3fC5B0eq/MBMSrgu8Bww309pk5jghyRfzp9YsG0ONo1CXUm2brQo/eRve7i9aDbiGXiEK0ch0BO7AvZPGMhHtYRrrOro4QrDVHGUgAF5SA1LD4dv/2AB8ctHwn4YbUmICieqlhJhprx3CNOJ086g7vPQOr21T4IbvtTumFaTibfoD3GWHQo11CvE04z3cN3rRERriP7bww/tZOe8OAMFGWANkmOJHwPPwEo1NBr6w3GD2VObEmqNhXeNc6rrM23Vm1JU40Hl+lVKubnbT1vujdGLmOpDY0GdekscQQrETEQJfhKlXIT0wwyPoLwR+Ja+GjyOhBr0nfWVoVoVrcTUwAk5pStBMt+5OwDRpP29L1+BL9eMwDgKpjVXRTh4MGagKYmFc6eKDf6jV0Yt9pG+jevv5IuyhwX0TRtfQCGgRTtS7oxhDQPxGqu01rr+aI7vGMfRQpaKEEXEWVmMaqCmktyUV35evK9h/xv1Yif00XBll55ShxN8t2/PnATvZxFKQfjJe5f/monbwf8rpfXHuFoh8M9hzjbcS5eh/TPYZZu1KltpeHSIAh5C+4aFyZw0e1DeAg/wdRO3PhBrVztsHSyISHlRdfEyw7QF4Lemr++2MVR1dTxS2I5mUEHjh+hmp64euH1Vb/RUppXlmE8t1RYYXfcsF2DlRwPswP739E/EpVtY3Syf/zOTyHyrOJBldzw22sauIzt8Q5Fe5qA/hGRWiejjK31P/P5j7wEKY7vrOJB1LWNXHSuSjffx9Ai9E","iv":"d5dc0252424ac0c08ca49ef320d09569","salt":"feAPSGdL4R2MVj2urJFl4A==","lib":"original"}', keyrings: [ { - accounts: ['0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3'], + accounts: [DEFAULT_FIXTURE_ACCOUNT], index: 0, type: 'HD Key Tree', }, @@ -244,7 +248,7 @@ class FixtureBuilder { internalAccounts: { accounts: { '4d7a5e0b-b261-4aed-8126-43972b0fa0a1': { - address: '0x76cf1cdd1fcc252442b50d6e97207228aa4aefc3', + address: DEFAULT_FIXTURE_ACCOUNT, id: '4d7a5e0b-b261-4aed-8126-43972b0fa0a1', metadata: { name: 'Account 1', @@ -270,15 +274,15 @@ class FixtureBuilder { PreferencesController: { featureFlags: {}, identities: { - '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3': { - address: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + [DEFAULT_FIXTURE_ACCOUNT]: { + address: DEFAULT_FIXTURE_ACCOUNT, name: 'Account 1', importTime: 1684232000456, }, }, ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, - selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + selectedAddress: DEFAULT_FIXTURE_ACCOUNT, useTokenDetection: true, useNftDetection: true, displayNftMedia: true, @@ -291,15 +295,15 @@ class FixtureBuilder { featureFlags: {}, frequentRpcList: [], identities: { - '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3': { - address: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + [DEFAULT_FIXTURE_ACCOUNT]: { + address: DEFAULT_FIXTURE_ACCOUNT, name: 'Account 1', importTime: 1684232000456, }, }, ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, - selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + selectedAddress: DEFAULT_FIXTURE_ACCOUNT, useTokenDetection: true, useNftDetection: false, displayNftMedia: true, @@ -692,9 +696,8 @@ class FixtureBuilder { const { providerConfig } = data; // Generate a unique key for the new network client ID - const newNetworkClientId = `networkClientId${ - Object.keys(networkController.networkConfigurationsByChainId).length + 1 - }`; + const newNetworkClientId = `networkClientId${Object.keys(networkController.networkConfigurationsByChainId).length + 1 + }`; // Define the network configuration const networkConfig = { @@ -746,7 +749,7 @@ class FixtureBuilder { caveats: [ { type: 'restrictReturnedAccounts', - value: ['0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3'], + value: [DEFAULT_FIXTURE_ACCOUNT], }, ], date: 1664388714636, @@ -815,10 +818,9 @@ class FixtureBuilder { const fixtures = this.fixture.state.engine.backgroundState; // Generate a unique key for the new network client ID - const newNetworkClientId = `networkClientId${ - Object.keys(fixtures.NetworkController.networkConfigurationsByChainId) - .length + 1 - }`; + const newNetworkClientId = `networkClientId${Object.keys(fixtures.NetworkController.networkConfigurationsByChainId) + .length + 1 + }`; // Define the Ganache network configuration const ganacheNetworkConfig = { @@ -854,10 +856,9 @@ class FixtureBuilder { const sepoliaConfig = CustomNetworks.Sepolia.providerConfig; // Generate a unique key for the new network client ID - const newNetworkClientId = `networkClientId${ - Object.keys(fixtures.NetworkController.networkConfigurationsByChainId) - .length + 1 - }`; + const newNetworkClientId = `networkClientId${Object.keys(fixtures.NetworkController.networkConfigurationsByChainId) + .length + 1 + }`; // Define the Sepolia network configuration const sepoliaNetworkConfig = { @@ -907,9 +908,8 @@ class FixtureBuilder { } = network.providerConfig; // Generate a unique key for the new network client ID - const newNetworkClientId = `networkClientId${ - Object.keys(networkConfigurationsByChainId).length + 1 - }`; + const newNetworkClientId = `networkClientId${Object.keys(networkConfigurationsByChainId).length + 1 + }`; // Define the network configuration const networkConfig = { @@ -970,7 +970,7 @@ class FixtureBuilder { keyrings: [ { type: 'HD Key Tree', - accounts: ['0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3'], + accounts: [DEFAULT_FIXTURE_ACCOUNT], }, { type: 'Simple Key Pair', @@ -983,6 +983,34 @@ class FixtureBuilder { return this; } + withTokens(tokens) { + merge(this.fixture.state.engine.backgroundState.TokensController, { + allTokens: { + [CHAIN_IDS.MAINNET]: { + [DEFAULT_FIXTURE_ACCOUNT]: tokens, + } + } + }); + return this; + } + + withIncomingTransactionPreferences(incomingTransactionPreferences) { + merge( + this.fixture.state.engine.backgroundState.PreferencesController, + { + showIncomingTransactions: incomingTransactionPreferences, + }, + ); + return this; + } + + withTransactions(transactions) { + merge(this.fixture.state.engine.backgroundState.TransactionController, { + transactions, + }); + return this; + } + /** * Build and return the fixture object. * @returns {Object} - The built fixture object. diff --git a/e2e/pages/Transactions/ActivitiesView.js b/e2e/pages/Transactions/ActivitiesView.js index 1b88464414c..089bb7f7839 100644 --- a/e2e/pages/Transactions/ActivitiesView.js +++ b/e2e/pages/Transactions/ActivitiesView.js @@ -1,4 +1,4 @@ -import { ActivitiesViewSelectorsText } from '../../selectors/Transactions/ActivitiesView.selectors'; +import { ActivitiesViewSelectorsIDs, ActivitiesViewSelectorsText } from '../../selectors/Transactions/ActivitiesView.selectors'; import Matchers from '../../utils/Matchers'; import Gestures from '../../utils/Gestures'; @@ -7,6 +7,12 @@ class ActivitiesView { return Matchers.getElementByText(ActivitiesViewSelectorsText.TITLE); } + get container() { + return Matchers.getElementByID( + ActivitiesViewSelectorsIDs.CONTAINER, + ); + } + generateSwapActivityLabel(sourceToken, destinationToken) { let title = ActivitiesViewSelectorsText.SWAP; title = title.replace('{{sourceToken}}', sourceToken); @@ -24,6 +30,10 @@ class ActivitiesView { const element = this.swapActivity(sourceToken, destinationToken); await Gestures.waitAndTap(element); } + + async swipeDown() { + await Gestures.swipe(this.container, 'down', 'slow', 0.5); + } } export default new ActivitiesView(); diff --git a/e2e/selectors/Transactions/ActivitiesView.selectors.js b/e2e/selectors/Transactions/ActivitiesView.selectors.js index 3f352595387..50a3cb95384 100644 --- a/e2e/selectors/Transactions/ActivitiesView.selectors.js +++ b/e2e/selectors/Transactions/ActivitiesView.selectors.js @@ -4,6 +4,10 @@ function getSentUnitMessage(unit) { return enContent.transactions.sent_unit.replace('{{unit}}', unit); } +export const ActivitiesViewSelectorsIDs = { + CONTAINER: 'transactions-container', +}; + export const ActivitiesViewSelectorsText = { CONFIRM_TEXT: enContent.transaction.confirmed, INCREASE_ALLOWANCE_METHOD: enContent.transactions.increase_allowance, diff --git a/e2e/specs/wallet/incoming-transactions.spec.js b/e2e/specs/wallet/incoming-transactions.spec.js new file mode 100644 index 00000000000..2cc5adf82d7 --- /dev/null +++ b/e2e/specs/wallet/incoming-transactions.spec.js @@ -0,0 +1,202 @@ +'use strict'; +import { SmokeCore } from '../../tags'; +import TestHelpers from '../../helpers'; +import { loginToApp } from '../../viewHelper'; +import Assertions from '../../utils/Assertions'; +import { startMockServer, stopMockServer } from '../../api-mocking/mock-server'; +import { withFixtures } from '../../fixtures/fixture-helper'; +import FixtureBuilder, { DEFAULT_FIXTURE_ACCOUNT } from '../../fixtures/fixture-builder'; +import ActivitiesView from '../../pages/Transactions/ActivitiesView'; +import TabBarComponent from '../../pages/wallet/TabBarComponent'; +import ToastModal from '../../pages/wallet/ToastModal'; + +const TOKEN_SYMBOL_MOCK = 'ABC'; +const TOKEN_ADDRESS_MOCK = '0x123'; + +const RESPONSE_STANDARD_MOCK = { + hash: '0x123456', + timestamp: new Date().toISOString(), + chainId: 1, + blockNumber: 1, + blockHash: '0x2', + gas: 1, + gasUsed: 1, + gasPrice: '1', + effectiveGasPrice: '1', + nonce: 1, + cumulativeGasUsed: 1, + methodId: null, + value: '1230000000000000000', + to: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), + from: '0x2', + isError: false, + valueTransfers: [], +}; + +const RESPONSE_STANDARD_2_MOCK = { + ...RESPONSE_STANDARD_MOCK, + timestamp: new Date().toISOString(), + hash: '0x2', + value: '2340000000000000000', +}; + +const RESPONSE_TOKEN_TRANSFER_MOCK = { + ...RESPONSE_STANDARD_MOCK, + to: '0x2', + valueTransfers: [ + { + contractAddress: TOKEN_ADDRESS_MOCK, + decimal: 18, + symbol: TOKEN_SYMBOL_MOCK, + from: '0x2', + to: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), + amount: '4560000000000000000', + }, + ], +}; + +const RESPONSE_OUTGOING_TRANSACTION_MOCK = { + ...RESPONSE_STANDARD_MOCK, + to: '0x2', + from: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), +}; + +function mockAccountsApi(transactions) { + return { + urlEndpoint: `https://accounts.api.cx.metamask.io/v1/accounts/${DEFAULT_FIXTURE_ACCOUNT}/transactions?networks=0x1&sortDirection=ASC`, + response: { + data: transactions ?? [RESPONSE_STANDARD_MOCK, RESPONSE_STANDARD_2_MOCK], + pageInfo: { + count: 2, + hasNextPage: false + } + }, + responseCode: 200, + }; +} + +describe(SmokeCore('Incoming Transactions'), () => { + + beforeAll(async () => { + jest.setTimeout(2500000); + await TestHelpers.reverseServerPort(); + }); + + it('displays standard incoming transaction', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + testSpecificMock: { + GET: [mockAccountsApi()] + } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await Assertions.checkIfTextIsDisplayed('Received ETH'); + await Assertions.checkIfTextIsDisplayed(/.*1\.23 ETH.*/); + await Assertions.checkIfTextIsDisplayed(/.*2\.34 ETH.*/); + } + ); + }); + + it('displays incoming token transfers', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().withTokens([{ + address: TOKEN_ADDRESS_MOCK, decimals: 18, symbol: TOKEN_SYMBOL_MOCK + }]).build(), + restartDevice: true, + testSpecificMock: { GET: [mockAccountsApi([RESPONSE_TOKEN_TRANSFER_MOCK])] } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await Assertions.checkIfTextIsDisplayed('Received ABC'); + await Assertions.checkIfTextIsDisplayed(/.*4\.56 ABC.*/); + } + ); + }); + + it('displays outgoing transactions', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + testSpecificMock: { GET: [mockAccountsApi([RESPONSE_OUTGOING_TRANSACTION_MOCK])] } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await Assertions.checkIfTextIsDisplayed('Sent ETH'); + await Assertions.checkIfTextIsDisplayed(/.*1\.23 ETH.*/); + } + ); + }); + + it('displays nothing if incoming transactions disabled', async () => { + await withFixtures( + { + fixture: new FixtureBuilder() + .withIncomingTransactionPreferences({ + '0x1': false + }) + .build(), + restartDevice: true, + testSpecificMock: { GET: [mockAccountsApi()] } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await TestHelpers.delay(2000); + await Assertions.checkIfTextIsNotDisplayed('Received ETH'); + } + ); + }); + + it('displays nothing if incoming transaction is a duplicate', async () => { + await withFixtures( + { + fixture: new FixtureBuilder() + .withTransactions([{ + hash: RESPONSE_STANDARD_MOCK.hash, + txParams: { + from: RESPONSE_STANDARD_MOCK.from + } + }]) + .build(), + restartDevice: true, + testSpecificMock: { GET: [mockAccountsApi([RESPONSE_STANDARD_MOCK])] } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await TestHelpers.delay(2000); + await Assertions.checkIfTextIsNotDisplayed('Received ETH'); + } + ); + }); + + it('displays notification', async () => { + await withFixtures( + { + fixture: new FixtureBuilder() + .build(), + restartDevice: true, + testSpecificMock: { GET: [mockAccountsApi()] } + }, + async () => { + await loginToApp(); + await TabBarComponent.tapActivity(); + await ActivitiesView.swipeDown(); + await Assertions.checkIfElementToHaveText(await ToastModal.notificationTitle, 'You received 1.23 ETH'); + } + ); + }); +}); diff --git a/package.json b/package.json index b11e4906799..5ec83c6c2b4 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/stake-sdk": "^0.3.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^11.0.0", - "@metamask/transaction-controller": "^41.0.0", + "@metamask/transaction-controller": "^42.0.0", "@metamask/utils": "^10.0.1", "@ngraveio/bc-ur": "^1.1.6", "@notifee/react-native": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index e295783628c..fb78b1da210 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5445,10 +5445,10 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-8.9.0.tgz#bac680e8f0007b3a11440f7e311674d6457d37ed" integrity sha512-N/WfmdrzJm+xbpuqJsfMrlrAhiNDsllIpwt9gDDeEKDlQAfJnMtT9xvOvBJbXY7zgMdtGZuD+KY64jNKabbuVQ== -"@metamask/transaction-controller@^41.0.0": - version "41.1.0" - resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-41.1.0.tgz#ad226b3f754750a064175075554b60a0d755a7f3" - integrity sha512-5u7tnl7WOY+Nuw8zXoeIikW7zQSxHduYsXDs2kJSAJo0qaOnFBgER031bqB23TIX2nLh8MR8vPf3Ft5ZBr7/UQ== +"@metamask/transaction-controller@^42.0.0": + version "42.0.0" + resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-42.0.0.tgz#f5c035d018b7f72e4b21757bd075c6863a6301ca" + integrity sha512-lITyvFsrjUhJox5CypaT7B80Bs5VxOziul2dcSBJFrD56vOX46ijq7FelTGbuSegJ+hlc+BUIsSSmhMiSDgHhw== dependencies: "@ethereumjs/common" "^3.2.0" "@ethereumjs/tx" "^4.2.0"