diff --git a/.iyarc b/.iyarc index abbf0e189d2..7fb6e40287c 100644 --- a/.iyarc +++ b/.iyarc @@ -1,3 +1,7 @@ # ReDoS vulnerability, no impact to this application, and fix not backported yet to the versions we use GHSA-c2qf-rxjj-qqgw + +# Sentry SDK Prototype Pollution gadget in JavaScript SDKs + +GHSA-593m-55hh-j8gv diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b50b2835a3..97b9b7095f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,96 @@ ## Current Main Branch +## 7.32.0 - Sep 19, 2024 + +### Added + +- [#10294](https://github.com/MetaMask/metamask-mobile/pull/10294): feat: create redux slice for featureFlags (#10294) +- [#11314](https://github.com/MetaMask/metamask-mobile/pull/11314): feat: reject connection properly (#11314) +- [#11132](https://github.com/MetaMask/metamask-mobile/pull/11132): feat: Add performance tracing infrastructure (#11132) +- [#10061](https://github.com/MetaMask/metamask-mobile/pull/10061): feat: new receive flow (#10061) +- [#11174](https://github.com/MetaMask/metamask-mobile/pull/11174): feat(2796): behind feature flag permission settings multichain 2of2 (#11174) +- [#11019](https://github.com/MetaMask/metamask-mobile/pull/11019): feat(2793): mocked UI screen displaying multichain dapp permission summary 2of2 (#11019) +- [#10988](https://github.com/MetaMask/metamask-mobile/pull/10988): feat(2808): add a mocked UI checkbox list that will later allow adding the ability to edit network permission (#10988) +- [#11168](https://github.com/MetaMask/metamask-mobile/pull/11168): feat: add pooled staking input flow screen (#11168) +- [#10964](https://github.com/MetaMask/metamask-mobile/pull/10964): feat: build your earnings component stub in eth token details (#10964) +- [#11051](https://github.com/MetaMask/metamask-mobile/pull/11051): feat: add brand evo font files (#11051) +- [#11285](https://github.com/MetaMask/metamask-mobile/pull/11285): feat: notifications add analytics (#11285) +- [#10755](https://github.com/MetaMask/metamask-mobile/pull/10755): feat: ledger account selection screen add hd options to sync with extension (#10755) +- [#11195](https://github.com/MetaMask/metamask-mobile/pull/11195): feat: add AppState dependency to load notifications (#11195) +- [#11175](https://github.com/MetaMask/metamask-mobile/pull/11175): feat: add product announcements toggle (#11175) + +### Changed +- [#11148](https://github.com/MetaMask/metamask-mobile/pull/11148): chore: remove animation and add new splash screen (#11148) +- [#11306](https://github.com/MetaMask/metamask-mobile/pull/11306): chore: update @sentry/react-native to version 5.33.0 (#11306) +- [#11144](https://github.com/MetaMask/metamask-mobile/pull/11144): test: E2E Mocking Setup For Detox Tests (#11144) +- [#11212](https://github.com/MetaMask/metamask-mobile/pull/11212): chore: Update CI workflow triggers to support release branches (#11212) +- [#11243](https://github.com/MetaMask/metamask-mobile/pull/11243): chore(js-ts): Convert ModalNavbarTitle to TypeScript (#11243) +- [#11213](https://github.com/MetaMask/metamask-mobile/pull/11213): test: Appium separate and optimize app launch time measurements (#11213) +- [#11264](https://github.com/MetaMask/metamask-mobile/pull/11264): chore: remove triggers for actions not needed during the merge-queue CI (#11264) +- [#11222](https://github.com/MetaMask/metamask-mobile/pull/11222): chore: add bitrise document link to the bitrise failed comment (#11222) +- [#11145](https://github.com/MetaMask/metamask-mobile/pull/11145): chore: update performance for new allocation (#11145) +- [#11184](https://github.com/MetaMask/metamask-mobile/pull/11184): test: remove notifications launch arg in E2E (#11184) +- [#11186](https://github.com/MetaMask/metamask-mobile/pull/11186): ci: prevent detox E2E lock failure (#11186) +- [#11141](https://github.com/MetaMask/metamask-mobile/pull/11141): chore: update express for all the packages (#11141) +- [#11124](https://github.com/MetaMask/metamask-mobile/pull/11124): docs: Update Appium documentation (#11124) +- [#10865](https://github.com/MetaMask/metamask-mobile/pull/10865): chore: update eslint v^8.44 (#10865) +- [#11096](https://github.com/MetaMask/metamask-mobile/pull/11096): test: detox black list gas api endpoint (#11096) +- [#11246](https://github.com/MetaMask/metamask-mobile/pull/11246): chore: Remove `eth-sign` (#11246) +- [#11220](https://github.com/MetaMask/metamask-mobile/pull/11220): chore: Update package @blockaid/ppom_release to version 1.5.3 (#11220) +- [#11244](https://github.com/MetaMask/metamask-mobile/pull/11244): chore(js-ts): Convert useInterval.js to TypeScript (#11244) +- [#11089](https://github.com/MetaMask/metamask-mobile/pull/11089): chore: add staking team to codeowners file (#11089) +- [#11049](https://github.com/MetaMask/metamask-mobile/pull/11049): chore: update balance design (#11049) +- [#11011](https://github.com/MetaMask/metamask-mobile/pull/11011): chore: Capture currency change in MetaMetrics (#11011) +- [#10468](https://github.com/MetaMask/metamask-mobile/pull/10468): chore: Capture custom rpc url in `trackEvent` (#10468) +- [#11207](https://github.com/MetaMask/metamask-mobile/pull/11207): chore(deps): Bump `@metamask/base-controller` from `^6.0.0` to `^7.0.0` (#11207) +- [#11235](https://github.com/MetaMask/metamask-mobile/pull/11235): ci: avoid running release pipeline on every commit to the release branch (#11235) +- [#11094](https://github.com/MetaMask/metamask-mobile/pull/11094): chore: chore/7.31.0-Changelog (#11094) +- [#10788](https://github.com/MetaMask/metamask-mobile/pull/10788): chore: Add `@metamask/selected-network-controller` & integrate (#10788) +- [#11122](https://github.com/MetaMask/metamask-mobile/pull/11122): test: e2e for auto-lock (#11122) +- [#11143](https://github.com/MetaMask/metamask-mobile/pull/11143): chore: bump react native webview to 14.0.3 version (#11143) +- [#11284](https://github.com/MetaMask/metamask-mobile/pull/11284): chore: add notifications state awareness inapp badge (#11284) +- [#11209](https://github.com/MetaMask/metamask-mobile/pull/11209): chore(runway): cherry-pick fix: freeze during swap with approval (#11209) +- [#11157](https://github.com/MetaMask/metamask-mobile/pull/11157): chore(runway): cherry-pick chore: bump send for all the packages (#11157) +- [#11082](https://github.com/MetaMask/metamask-mobile/pull/11082): chore: bump network controller 20.0.0 (#11082) +- [#11095](https://github.com/MetaMask/metamask-mobile/pull/11095): chore(runway): cherry-pick fix: Intermittent Display Issue of Fiat Currency on Main Wallet View (#11095) +- [#11181](https://github.com/MetaMask/metamask-mobile/pull/11181): chore(runway): cherry-pick fix: fix check token balance is zero (#11181) +- [#11208](https://github.com/MetaMask/metamask-mobile/pull/11208): chore(runway): cherry-pick chore: update performance for new allocation (#11208) +- [#10821](https://github.com/MetaMask/metamask-mobile/pull/10821): chore(deps): bump `accounts-controller` to v18.1.0 and `keyring-api` to v8.1.0 (#10821) + +### Fixed +- [#11117](https://github.com/MetaMask/metamask-mobile/pull/11117): fix: add feat flag (#11117) +- [#11084](https://github.com/MetaMask/metamask-mobile/pull/11084): fix: locks api spec version for api spec tests (#11084) +- [#11310](https://github.com/MetaMask/metamask-mobile/pull/11310): fix: quick fix on feature flag & notification state (#11310) +- [#11200](https://github.com/MetaMask/metamask-mobile/pull/11200): fix: add feature flag on profile sync (#11200) +- [#11302](https://github.com/MetaMask/metamask-mobile/pull/11302): fix: cp & resolve merge conflict (#11302) +- [#11130](https://github.com/MetaMask/metamask-mobile/pull/11130): fix(action): add a workaround for known bots (#11130) +- [#11173](https://github.com/MetaMask/metamask-mobile/pull/11173): fix: dset version (#11173) +- [#10899](https://github.com/MetaMask/metamask-mobile/pull/10899): fix: Android crash when svgs use the " html entity (#10899) +- [#11126](https://github.com/MetaMask/metamask-mobile/pull/11126): fix: Skip sonar cloud gate in step instead (#11126) +- [#11121](https://github.com/MetaMask/metamask-mobile/pull/11121): fix: Add new job to verify ""All jobs pass"" job for required PR check (#11121) +- [#11266](https://github.com/MetaMask/metamask-mobile/pull/11266): fix: notification permission flow (#11266) +- [#11252](https://github.com/MetaMask/metamask-mobile/pull/11252): fix: notification permission request message (#11252) +- [#11155](https://github.com/MetaMask/metamask-mobile/pull/11155): fix: android crashing on date formating Intl usage. (#11155) +- [#11137](https://github.com/MetaMask/metamask-mobile/pull/11137): fix: notifications bugs (#11137) +- [#11110](https://github.com/MetaMask/metamask-mobile/pull/11110): fix: accounts notifications switch (#11110) +- [#11146](https://github.com/MetaMask/metamask-mobile/pull/11146): fix: update nativesdk with improved concurrency handling (#11146) +- [#11165](https://github.com/MetaMask/metamask-mobile/pull/11165): fix: freeze during swap with approval (#11165) +- [#11161](https://github.com/MetaMask/metamask-mobile/pull/11161): fix: blockaid loader on confirmation pages (#11161) +- [#10989](https://github.com/MetaMask/metamask-mobile/pull/10989): fix: closing of gas info tooltip (#10989) +- [#10348](https://github.com/MetaMask/metamask-mobile/pull/10348): fix: confirmations UI adjustments (#10348) +- [#10842](https://github.com/MetaMask/metamask-mobile/pull/10842): fix: app crash due to minimal input must be string error (#10842) +- [#11112](https://github.com/MetaMask/metamask-mobile/pull/11112): fix: update token details monetization button (#11112) +- [#11172](https://github.com/MetaMask/metamask-mobile/pull/11172): fix: fix check token balance is zero (#11172) +- [#11087](https://github.com/MetaMask/metamask-mobile/pull/11087): fix: Intermittent Display Issue of Fiat Currency on Main Wallet View (#11087) +- [#11176](https://github.com/MetaMask/metamask-mobile/pull/11176): fix: switch from bundled to url EE (#11176) +- [#11281](https://github.com/MetaMask/metamask-mobile/pull/11281): fix: Fix the styling issue of link in SearchingForDeviceStep component (#11281) +- [#11265](https://github.com/MetaMask/metamask-mobile/pull/11265): fix: notification account syncing (#11265) +- [#11218](https://github.com/MetaMask/metamask-mobile/pull/11218): fix: close icon on notifications list screen (#11218) +- [#11193](https://github.com/MetaMask/metamask-mobile/pull/11193): fix: ItemMenu crash using dayjs (#11193) +- [#11098](https://github.com/MetaMask/metamask-mobile/pull/11098): fix: badge count and ui polishing (#11098) + + ## 7.31.0 - Sep 6, 2024 ### Added - [#10747](https://github.com/MetaMask/metamask-mobile/pull/10747): feat: 2805 grant permission to network with missmatching rpc url (#10747) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2363b3c882e..7ff5e6cd321 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,8 +173,8 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1432 - versionName "7.31.0" + versionCode 1444 + versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/component-library/components/Icons/Icon/scripts/generate-assets.js b/app/component-library/components/Icons/Icon/scripts/generate-assets.ts similarity index 97% rename from app/component-library/components/Icons/Icon/scripts/generate-assets.js rename to app/component-library/components/Icons/Icon/scripts/generate-assets.ts index 5c34baab0bf..22c90fe0523 100644 --- a/app/component-library/components/Icons/Icon/scripts/generate-assets.js +++ b/app/component-library/components/Icons/Icon/scripts/generate-assets.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node /* eslint-disable import/no-commonjs, import/no-nodejs-modules, import/no-nodejs-modules, no-console */ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; const ASSETS_FOLDER = 'assets'; const GENERATED_ASSETS_FILE = 'Icon.assets.ts'; @@ -9,7 +9,7 @@ const TYPES_FILE = 'Icon.types.ts'; const ASSET_EXT = '.svg'; const TYPES_CONTENT_TO_DETECT = '// DO NOT EDIT - Use generate-assets.js'; -const getIconNameInTitleCase = (fileName) => +const getIconNameInTitleCase = (fileName: string) => path .basename(fileName, ASSET_EXT) .split('-') diff --git a/app/components/Base/ListItem.js b/app/components/Base/ListItem.tsx similarity index 64% rename from app/components/Base/ListItem.js rename to app/components/Base/ListItem.tsx index 2586e37be28..987b508650f 100644 --- a/app/components/Base/ListItem.js +++ b/app/components/Base/ListItem.tsx @@ -1,11 +1,19 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { StyleSheet, View } from 'react-native'; -import { fontStyles } from '../../styles/common'; +import { + StyleSheet, + View, + ViewProps, + TextProps, + StyleProp, + ViewStyle, + TextStyle, +} from 'react-native'; import Text from './Text'; import { useTheme } from '../../util/theme'; +import { Theme } from '@metamask/design-tokens'; +import { fontStyles } from '../../styles/common'; -const createStyles = (colors) => +const createStyles = (colors: Theme['colors']) => StyleSheet.create({ wrapper: { padding: 15, @@ -54,53 +62,81 @@ const createStyles = (colors) => }, }); -const ListItem = ({ style, ...props }) => { +interface ListItemProps extends ViewProps { + style?: StyleProp; +} + +interface ListItemTextProps extends TextProps { + style?: StyleProp; +} + +type ListItemComponent = React.FC & { + Date: React.FC; + Content: React.FC; + Actions: React.FC; + Icon: React.FC; + Body: React.FC; + Title: React.FC; + Amounts: React.FC; + Amount: React.FC; + FiatAmount: React.FC; +}; + +const ListItem: ListItemComponent = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemDate = ({ style, ...props }) => { +const ListItemDate: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemContent = ({ style, ...props }) => { + +const ListItemContent: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemActions = ({ style, ...props }) => { + +const ListItemActions: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemIcon = ({ style, ...props }) => { + +const ListItemIcon: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemBody = ({ style, ...props }) => { + +const ListItemBody: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemTitle = ({ style, ...props }) => { + +const ListItemTitle: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemAmounts = ({ style, ...props }) => { + +const ListItemAmounts: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemAmount = ({ style, ...props }) => { + +const ListItemAmount: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; }; -const ListItemFiatAmount = ({ style, ...props }) => { + +const ListItemFiatAmount: React.FC = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); return ; @@ -117,39 +153,3 @@ ListItem.Amount = ListItemAmount; ListItem.FiatAmount = ListItemFiatAmount; export default ListItem; - -/** - * Any other external style defined in props will be applied - */ -const stylePropType = PropTypes.oneOfType([PropTypes.object, PropTypes.array]); - -ListItem.propTypes = { - style: stylePropType, -}; -ListItemDate.propTypes = { - style: stylePropType, -}; -ListItemContent.propTypes = { - style: stylePropType, -}; -ListItemActions.propTypes = { - style: stylePropType, -}; -ListItemIcon.propTypes = { - style: stylePropType, -}; -ListItemBody.propTypes = { - style: stylePropType, -}; -ListItemTitle.propTypes = { - style: stylePropType, -}; -ListItemAmounts.propTypes = { - style: stylePropType, -}; -ListItemAmount.propTypes = { - style: stylePropType, -}; -ListItemFiatAmount.propTypes = { - style: stylePropType, -}; diff --git a/app/components/Base/ModalHandler.js b/app/components/Base/ModalHandler.tsx similarity index 52% rename from app/components/Base/ModalHandler.js rename to app/components/Base/ModalHandler.tsx index 805c787192a..fe7a245438f 100644 --- a/app/components/Base/ModalHandler.js +++ b/app/components/Base/ModalHandler.tsx @@ -1,6 +1,16 @@ +import { ReactNode } from 'react'; import useModalHandler from './hooks/useModalHandler'; -function ModalHandler({ children }) { +interface ModalHandlerProps { + children: ((props: { + isVisible: boolean; + toggleModal: () => void; + showModal: () => void; + hideModal: () => void; + }) => ReactNode); +} + +function ModalHandler({ children }: ModalHandlerProps) { const [isVisible, toggleModal, showModal, hideModal] = useModalHandler(false); if (typeof children === 'function') { diff --git a/app/components/Base/RemoteImage/index.test.tsx b/app/components/Base/RemoteImage/index.test.tsx index 64e352fa413..429dfeb7ca5 100644 --- a/app/components/Base/RemoteImage/index.test.tsx +++ b/app/components/Base/RemoteImage/index.test.tsx @@ -6,7 +6,7 @@ jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useSelector: jest .fn() - .mockImplementation(() => 'https://cloudflare-ipfs.com/ipfs/'), + .mockImplementation(() => 'https://gateway.pinata.cloud/ipfs/'), })); jest.mock('../../../components/hooks/useIpfsGateway', () => jest.fn()); diff --git a/app/components/UI/AssetIcon/index.test.tsx b/app/components/UI/AssetIcon/index.test.tsx index a553d618e34..f0451a2a5f5 100644 --- a/app/components/UI/AssetIcon/index.test.tsx +++ b/app/components/UI/AssetIcon/index.test.tsx @@ -10,7 +10,7 @@ const mockInitialState = { ...backgroundState, PreferencesController: { featureFlags: {}, - ipfsGateway: 'https://cloudflare-ipfs.com/ipfs/', + ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true, @@ -24,7 +24,7 @@ const mockInitialState = { _W: { featureFlags: {}, frequentRpcList: [], - ipfsGateway: 'https://cloudflare-ipfs.com/ipfs/', + ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true, diff --git a/app/components/UI/Swaps/components/TokenIcon.test.js b/app/components/UI/Swaps/components/TokenIcon.test.js index 015b0328237..f8f0bc4adb3 100644 --- a/app/components/UI/Swaps/components/TokenIcon.test.js +++ b/app/components/UI/Swaps/components/TokenIcon.test.js @@ -13,7 +13,7 @@ describe('TokenIcon component', () => { const icon = shallow( , ); expect(icon).toMatchSnapshot(); @@ -27,7 +27,7 @@ describe('TokenIcon component', () => { , ); expect(iconMedium).toMatchSnapshot(); diff --git a/app/components/UI/Swaps/components/TokenSelectButton.test.js b/app/components/UI/Swaps/components/TokenSelectButton.test.js index 421d84de6fd..328501a798a 100644 --- a/app/components/UI/Swaps/components/TokenSelectButton.test.js +++ b/app/components/UI/Swaps/components/TokenSelectButton.test.js @@ -19,7 +19,7 @@ describe('TokenSelectButton component', () => { , ); expect(icon).toMatchSnapshot(); @@ -27,7 +27,7 @@ describe('TokenSelectButton component', () => { , ); diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap index 7d76d1bfc00..8b00e7564fd 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap @@ -57,7 +57,7 @@ exports[`TokenIcon component should Render correctly 4`] = ` onError={[Function]} source={ { - "uri": "https://cloudflare-ipfs.com/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", + "uri": "https://gateway.pinata.cloud/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", } } style={ @@ -146,7 +146,7 @@ exports[`TokenIcon component should Render correctly 8`] = ` onError={[Function]} source={ { - "uri": "https://cloudflare-ipfs.com/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", + "uri": "https://gateway.pinata.cloud/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", } } style={ diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap index 56f38e96af9..8c6472bfbac 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap @@ -71,7 +71,7 @@ exports[`TokenSelectButton component should Render correctly 4`] = ` } > @@ -95,7 +95,7 @@ exports[`TokenSelectButton component should Render correctly 5`] = ` } > diff --git a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx new file mode 100644 index 00000000000..f18648f6054 --- /dev/null +++ b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { useSelector } from 'react-redux'; +import useIsOriginalNativeTokenSymbol from '../../../../hooks/useIsOriginalNativeTokenSymbol/useIsOriginalNativeTokenSymbol'; +import { useMetrics } from '../../../../hooks/useMetrics'; +import { useTheme } from '../../../../../util/theme'; +import AppConstants from '../../../../../core/AppConstants'; +import Engine from '../../../../../core/Engine'; +import Routes from '../../../../../constants/navigation/Routes'; +import { MetaMetricsEvents } from '../../../../../core/Analytics'; +import { + selectChainId, + selectProviderConfig, + selectTicker, +} from '../../../../../selectors/networkController'; +import { selectCurrentCurrency } from '../../../../../selectors/currencyRateController'; +import { RootState } from '../../../../../reducers'; +import { renderFiat } from '../../../../../util/number'; +import { isTestNet } from '../../../../../util/networks'; +import { isPortfolioUrl } from '../../../../../util/url'; +import createStyles from '../../styles'; +import Button, { + ButtonVariants, + ButtonSize, + ButtonWidthTypes, +} from '../../../../../component-library/components/Buttons/Button'; +import Text from '../../../../../component-library/components/Texts/Text'; +import AggregatedPercentage from '../../../../../component-library/components-temp/Price/AggregatedPercentage'; +import { IconName } from '../../../../../component-library/components/Icons/Icon'; +import { BrowserTab } from '../../types'; +import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors'; +import { strings } from '../../../../../../locales/i18n'; + +export const PortfolioBalance = () => { + const { colors } = useTheme(); + const styles = createStyles(colors); + const balance = Engine.getTotalFiatAccountBalance(); + const navigation = useNavigation(); + const { trackEvent, isEnabled } = useMetrics(); + + const { type } = useSelector(selectProviderConfig); + const chainId = useSelector(selectChainId); + const ticker = useSelector(selectTicker); + const isDataCollectionForMarketingEnabled = useSelector( + (state: RootState) => state.security.dataCollectionForMarketing, + ); + const currentCurrency = useSelector(selectCurrentCurrency); + const browserTabs = useSelector((state: RootState) => state.browser.tabs); + + const isOriginalNativeTokenSymbol = useIsOriginalNativeTokenSymbol( + chainId, + ticker, + type, + ); + + let total; + if (isOriginalNativeTokenSymbol) { + const tokenFiatTotal = balance?.tokenFiat ?? 0; + const ethFiatTotal = balance?.ethFiat ?? 0; + total = tokenFiatTotal + ethFiatTotal; + } else { + total = balance?.tokenFiat ?? 0; + } + + const fiatBalance = `${renderFiat(total, currentCurrency)}`; + + const onOpenPortfolio = () => { + const existingPortfolioTab = browserTabs.find(({ url }: BrowserTab) => + isPortfolioUrl(url), + ); + + let existingTabId; + let newTabUrl; + if (existingPortfolioTab) { + existingTabId = existingPortfolioTab.id; + } else { + const analyticsEnabled = isEnabled(); + const portfolioUrl = new URL(AppConstants.PORTFOLIO.URL); + + portfolioUrl.searchParams.append('metamaskEntry', 'mobile'); + + // Append user's privacy preferences for metrics + marketing on user navigation to Portfolio. + portfolioUrl.searchParams.append( + 'metricsEnabled', + String(analyticsEnabled), + ); + portfolioUrl.searchParams.append( + 'marketingEnabled', + String(!!isDataCollectionForMarketingEnabled), + ); + + newTabUrl = portfolioUrl.href; + } + const params = { + ...(newTabUrl && { newTabUrl }), + ...(existingTabId && { existingTabId, newTabUrl: undefined }), + timestamp: Date.now(), + }; + navigation.navigate(Routes.BROWSER.HOME, { + screen: Routes.BROWSER.VIEW, + params, + }); + trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { + portfolioUrl: AppConstants.PORTFOLIO.URL, + }); + }; + + return ( + + + + {fiatBalance} + + + {!isTestNet(chainId) ? ( + + ) : null} + +