diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e172ce58d0..9c4bdcaa9a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,73 @@ ## Current Main Branch +## 7.36.0 - Nov 15, 2024 +### Added +- [#12015](https://github.com/MetaMask/metamask-mobile/pull/12015): feat: 1957 crash screen redesign (#12015) +- [#12186](https://github.com/MetaMask/metamask-mobile/pull/12186): feat (cherry-pick): display staking transaction methods (#12110) (#12186) +- [#12110](https://github.com/MetaMask/metamask-mobile/pull/12110): feat: display staking transaction methods (#12110) +- [#12290](https://github.com/MetaMask/metamask-mobile/pull/12290): feat: STAKE-827: track additional pooled staking events (#12290) +- [#12280](https://github.com/MetaMask/metamask-mobile/pull/12280): feat: add loading skeleton for staking banners (#12280) +- [#12245](https://github.com/MetaMask/metamask-mobile/pull/12245): feat: add gas impact modal to stake confirmation input view (#12245) +- [#12263](https://github.com/MetaMask/metamask-mobile/pull/12263): feat: conditionally display stake/earn text based on pooled staking feature flag (#12261) (#12263) +- [#12146](https://github.com/MetaMask/metamask-mobile/pull/12146): feat: add staked ETH to metamask mobile homepage and account list menu (#12146) +- [#12261](https://github.com/MetaMask/metamask-mobile/pull/12261): feat: conditionally display stake/earn text based on pooled staking feature flag (#12261) +- [#12247](https://github.com/MetaMask/metamask-mobile/pull/12247): feat: update input colors and text formatting (#12247) +- [#12210](https://github.com/MetaMask/metamask-mobile/pull/12210): chore: disable pooled staking feature flag (#12210) +- [#12144](https://github.com/MetaMask/metamask-mobile/pull/12144): feat: add staking events (#12144) +- [#12268](https://github.com/MetaMask/metamask-mobile/pull/12268): feat: multichain currency rate polling (#12268) +- [#11808](https://github.com/MetaMask/metamask-mobile/pull/11808): feat: Token Network Filter UI [Mobile] (#11808) +- [#12171](https://github.com/MetaMask/metamask-mobile/pull/12171): feat: multichain polling hook (#12171) +- [#12168](https://github.com/MetaMask/metamask-mobile/pull/12168): feat(2808): improvements-and-small-features-and-small-fixes-that-still-needed-to-be-added-to-edit-permissions (#12168) +- [#11590](https://github.com/MetaMask/metamask-mobile/pull/11590): feat(2796): permission settings replace some of the mock data by real data (#11590) +- [#11511](https://github.com/MetaMask/metamask-mobile/pull/11511): feat: display snap name (#11511) +- [#12145](https://github.com/MetaMask/metamask-mobile/pull/12145): feat: disable wallet buttons for accounts that cannot sign transactions (#12145) +- [#12057](https://github.com/MetaMask/metamask-mobile/pull/12057): feat: team-label-token (#12057) +- [#11836](https://github.com/MetaMask/metamask-mobile/pull/11836): feat: upgrade @metamask/eth-ledger-bridge-keyring (#11836) + +### Changed +- [#11898](https://github.com/MetaMask/metamask-mobile/pull/11898): chore: New Crowdin translations by Github Action (#11898) +- [#12292](https://github.com/MetaMask/metamask-mobile/pull/12292): chore: Allow for higher versions of ruby (#12292) +- [#12291](https://github.com/MetaMask/metamask-mobile/pull/12291): chore: Remove notifications logic from wallet view (#12276) (#12291) +- [#12271](https://github.com/MetaMask/metamask-mobile/pull/12271): chore: Cache node installed via nvm on Bitrise (#12271) +- [#12121](https://github.com/MetaMask/metamask-mobile/pull/12121): chore: udpate LSMinimumSystemVersion (#12121) +- [#11658](https://github.com/MetaMask/metamask-mobile/pull/11658): chore: 8618 reduce enzyme usage in unit test by 25 (#11658) +- [#12257](https://github.com/MetaMask/metamask-mobile/pull/12257): refactor: remove global network usage from petnames (#12257) +- [#11996](https://github.com/MetaMask/metamask-mobile/pull/11996): chore: upgrade signature controller to remove global network (#11996) +- [#12274](https://github.com/MetaMask/metamask-mobile/pull/12274): chore: Update naming for returning a txHash asap for smart transactions (#12274) +- [#12287](https://github.com/MetaMask/metamask-mobile/pull/12287): docs: update onboarding readme (#12287) +- [#12234](https://github.com/MetaMask/metamask-mobile/pull/12234): chore: add unit test for native currency validation (#12234) +- [#12237](https://github.com/MetaMask/metamask-mobile/pull/12237): chore: Remove GoogleService files from git cache (#12237) +- [#12178](https://github.com/MetaMask/metamask-mobile/pull/12178): chore: upgrade assets-controllers to v41 (#12178) +- [#12209](https://github.com/MetaMask/metamask-mobile/pull/12209): chore: Modify gitignore to include generated ios/plist files (#12209) +- [#12286](https://github.com/MetaMask/metamask-mobile/pull/12286): chore: Add tags to UI Startup sentry transaction (#12286) +- [#12276](https://github.com/MetaMask/metamask-mobile/pull/12276): chore: Remove notifications logic from wallet view (#12276) +- [#12174](https://github.com/MetaMask/metamask-mobile/pull/12174): chore: Remove navigation instrumentation (#12174) +- [#12211](https://github.com/MetaMask/metamask-mobile/pull/12211): chore: disable pooled staking release for v7.35.0 (#12211) +- [#12194](https://github.com/MetaMask/metamask-mobile/pull/12194): chore: cicd error handling (#12194) +- [#12192](https://github.com/MetaMask/metamask-mobile/pull/12192): chore: fix release pr fixes (#12192) +- [#12175](https://github.com/MetaMask/metamask-mobile/pull/12175): chore: cicd - propagate changes to release pr from scripts (#12175) +- [#12225](https://github.com/MetaMask/metamask-mobile/pull/12225): chore: bump `@metamask/ppom-validator` to `0.35.1` (#12225) + +### Fixed +- [#12166](https://github.com/MetaMask/metamask-mobile/pull/12166): fix: remove SmokeNotifications tests for android on smoke tests pipeline (#12166) +- [#12217](https://github.com/MetaMask/metamask-mobile/pull/12217): fix: e2e: use different wallet SRP for non accounts tests (#12217) +- [#12197](https://github.com/MetaMask/metamask-mobile/pull/12197): fix: E2E: quarantine import-wallet-account tests (#12197) +- [#12250](https://github.com/MetaMask/metamask-mobile/pull/12250): fix: Add migration to fix NotificationServicesController bug (#12219) (#12250) +- [#12232](https://github.com/MetaMask/metamask-mobile/pull/12232): fix: e2e re-enable notifications android workflow (#12232) +- [#12219](https://github.com/MetaMask/metamask-mobile/pull/12219): fix: Add migration to fix NotificationServicesController bug (#12219) +- [#12120](https://github.com/MetaMask/metamask-mobile/pull/12120): fix: Onboarding failing biometrics locks screen for user instead of disabling biometrics and continuing with the onboarding (#12120) +- [#12177](https://github.com/MetaMask/metamask-mobile/pull/12177): fix: Create migration 59 to fix undefined selectedAccount (#12177) +- [#12311](https://github.com/MetaMask/metamask-mobile/pull/12311): fix: transaction reject crash (#12311) +- [#12228](https://github.com/MetaMask/metamask-mobile/pull/12228): fix: Update `transaction-controller` version (#12228) +- [#12100](https://github.com/MetaMask/metamask-mobile/pull/12100): fix: hide internal transaction origins in confirmation views (#12100) +- [#12283](https://github.com/MetaMask/metamask-mobile/pull/12283): fix: ensure unstake max will unstake all user shares (#12283) +- [#12231](https://github.com/MetaMask/metamask-mobile/pull/12231): fix: added ScrollView to stake confirmation review screen (#12231) +- [#12255](https://github.com/MetaMask/metamask-mobile/pull/12255): fix: fix displayed selected rpc for linea (#12255) +- [#11693](https://github.com/MetaMask/metamask-mobile/pull/11693): fix: relax network symbol length validation (#11693) +- [#12205](https://github.com/MetaMask/metamask-mobile/pull/12205): fix: add contractBalances as dependency (#12205) +- [#12235](https://github.com/MetaMask/metamask-mobile/pull/12235): fix: privacy mode is enabled in account selector by params (#12235) +- [#12282](https://github.com/MetaMask/metamask-mobile/pull/12282): fix: Lock ruby version to 3.1.6 and bump pod to 1.16.2 (#12282) ## 7.35.0 - Nov 15, 2024 ### Added - [#12078](https://github.com/MetaMask/metamask-mobile/pull/12078): chore(runway): cherry-pick feat: add favorites to browser menu (#12078) diff --git a/android/app/build.gradle b/android/app/build.gradle index 70cf56b3d519..30b32739e085 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 - versionName "7.35.0" - versionCode 1497 + versionCode 1501 + versionName "7.36.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.constants.ts b/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.constants.ts deleted file mode 100644 index 50b8da3111f9..000000000000 --- a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -export const SNAP_ACCOUNT_CUSTOM_NAME_APPROVAL = - 'snap-account-custom-name-approval'; -export const SNAP_ACCOUNT_CUSTOM_NAME_CANCEL_BUTTON = - 'snap-account-custom-name-approval-cancel-button'; -export const SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON = - 'snap-account-custom-name-approval-add-account-button'; -export const SNAP_ACCOUNT_CUSTOM_NAME_INPUT = - 'snap-account-custom-name-approval-input'; -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.styles.ts b/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.styles.ts deleted file mode 100644 index ce8c32319f50..000000000000 --- a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.styles.ts +++ /dev/null @@ -1,45 +0,0 @@ -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -import { StyleSheet } from 'react-native'; -import { Theme } from '../../../util/theme/models'; -import Device from '../../../util/device'; - -/** - * - * @param params Style sheet params. - * @param params.theme App theme from ThemeContext. - * @param params.vars Inputs that the style sheet depends on. - * @returns StyleSheet object. - */ -const styleSheet = (params: { theme: Theme }) => { - const { theme } = params; - const { colors } = theme; - return StyleSheet.create({ - root: { - backgroundColor: colors.background.default, - paddingTop: 24, - paddingHorizontal: 16, - borderTopLeftRadius: 20, - borderTopRightRadius: 20, - minHeight: 200, - paddingBottom: Device.isIphoneX() ? 20 : 0, - }, - actionContainer: { - flex: 0, - paddingVertical: 16, - justifyContent: 'center', - }, - inputTitle: { - textAlign: 'left', - }, - input: { - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: 4, - padding: 10, - marginVertical: 10, - }, - }); -}; - -export default styleSheet; -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.tsx b/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.tsx deleted file mode 100644 index 4ad320c1be81..000000000000 --- a/app/components/Approvals/SnapAccountCustomNameApproval/SnapAccountCustomNameApproval.tsx +++ /dev/null @@ -1,136 +0,0 @@ -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -import React, { useEffect, useState } from 'react'; -import { TextInput, View } from 'react-native'; -import ApprovalModal from '../ApprovalModal'; -import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; -import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../core/RPCMethods/RPCMethodMiddleware'; -import { - SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON, - SNAP_ACCOUNT_CUSTOM_NAME_APPROVAL, - SNAP_ACCOUNT_CUSTOM_NAME_CANCEL_BUTTON, - SNAP_ACCOUNT_CUSTOM_NAME_INPUT, -} from './SnapAccountCustomNameApproval.constants'; -import styleSheet from './SnapAccountCustomNameApproval.styles'; -import { useStyles } from '../../hooks/useStyles'; -import BottomSheetFooter, { - ButtonsAlignment, -} from '../../../component-library/components/BottomSheets/BottomSheetFooter'; -import SheetHeader from '../../../component-library/components/Sheet/SheetHeader'; -import { strings } from '../../../../locales/i18n'; -import Text, { - TextColor, - TextVariant, -} from '../../../component-library/components/Texts/Text'; -import { - ButtonProps, - ButtonSize, - ButtonVariants, -} from '../../../component-library/components/Buttons/Button/Button.types'; -import { useSelector } from 'react-redux'; -import { selectInternalAccounts } from '../../../selectors/accountsController'; -import { KeyringTypes } from '@metamask/keyring-controller'; -import Engine from '../../../core/Engine'; - -const SnapAccountCustomNameApproval = () => { - const { approvalRequest, onConfirm, onReject } = useApprovalRequest(); - const internalAccounts = useSelector(selectInternalAccounts); - const [accountName, setAccountName] = useState(''); - const [isNameTaken, setIsNameTaken] = useState(false); - - const { styles } = useStyles(styleSheet, {}); - - const onAddAccountPressed = () => { - if (!isNameTaken) { - onConfirm(undefined, { success: true, name: accountName }); - } - }; - - const checkIfNameTaken = (name: string) => - internalAccounts.some((account) => account.metadata.name === name); - - useEffect(() => { - function generateUniqueNameWithSuffix(baseName: string): string { - let suffix = 1; - let candidateName = baseName; - while ( - internalAccounts.some( - // eslint-disable-next-line no-loop-func - (account) => account.metadata.name === candidateName, - ) - ) { - suffix += 1; - candidateName = `${baseName} ${suffix}`; - } - return candidateName; - } - - const suggestedName = approvalRequest?.requestData.snapSuggestedAccountName; - const initialName = suggestedName - ? generateUniqueNameWithSuffix(suggestedName) - : Engine.context.AccountsController.getNextAvailableAccountName( - KeyringTypes.snap, - ); - setAccountName(initialName); - }, [approvalRequest, internalAccounts]); - - const cancelButtonProps: ButtonProps = { - variant: ButtonVariants.Secondary, - label: strings('accountApproval.cancel'), - size: ButtonSize.Lg, - onPress: onReject, - testID: SNAP_ACCOUNT_CUSTOM_NAME_CANCEL_BUTTON, - }; - - const addAccountButtonProps: ButtonProps = { - variant: ButtonVariants.Primary, - label: strings('snap_account_custom_name_approval.add_account_button'), - size: ButtonSize.Lg, - onPress: onAddAccountPressed, - testID: SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON, - isDisabled: isNameTaken, - }; - - const handleNameChange = (text: string) => { - setAccountName(text); - setIsNameTaken(checkIfNameTaken(text)); - }; - - return ( - - - - - {strings('snap_account_custom_name_approval.input_title')} - - - {isNameTaken && ( - - {strings('snap_account_custom_name_approval.name_taken_message')} - - )} - - - - - - ); -}; - -export default SnapAccountCustomNameApproval; -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/SnapAccountCustomNameApproval/index.ts b/app/components/Approvals/SnapAccountCustomNameApproval/index.ts deleted file mode 100644 index 6e3deb8308d3..000000000000 --- a/app/components/Approvals/SnapAccountCustomNameApproval/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -export { default } from './SnapAccountCustomNameApproval'; -///: END:ONLY_INCLUDE_IF diff --git a/app/components/Approvals/SnapAccountCustomNameApproval/test/SnapAccountCustomNameApproval.test.tsx b/app/components/Approvals/SnapAccountCustomNameApproval/test/SnapAccountCustomNameApproval.test.tsx deleted file mode 100644 index edff596c50fd..000000000000 --- a/app/components/Approvals/SnapAccountCustomNameApproval/test/SnapAccountCustomNameApproval.test.tsx +++ /dev/null @@ -1,297 +0,0 @@ -import React from 'react'; -import { fireEvent } from '@testing-library/react-native'; -import { - SNAP_ACCOUNT_CUSTOM_NAME_APPROVAL, - SNAP_ACCOUNT_CUSTOM_NAME_CANCEL_BUTTON, - SNAP_ACCOUNT_CUSTOM_NAME_INPUT, - SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON, -} from '../SnapAccountCustomNameApproval.constants'; -import { ApprovalRequest } from '@metamask/approval-controller'; -import { KeyringTypes } from '@metamask/keyring-controller'; -import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../core/RPCMethods/RPCMethodMiddleware'; -import SnapAccountCustomNameApproval from '../SnapAccountCustomNameApproval'; -import renderWithProvider, { - DeepPartial, -} from '../../../../util/test/renderWithProvider'; -import useApprovalRequest from '../../../Views/confirmations/hooks/useApprovalRequest'; -import Engine from '../../../../core/Engine'; -import { RootState } from '../../../../reducers'; -import { - MOCK_ACCOUNTS_CONTROLLER_STATE, - MOCK_ADDRESS_1, -} from '../../../../util/test/accountsControllerTestUtils'; - -jest.mock('../../../Views/confirmations/hooks/useApprovalRequest'); - -jest.mock('../../../../core/Engine', () => { - const { MOCK_ADDRESS_1: mockAddress1 } = jest.requireActual( - '../../../../util/test/accountsControllerTestUtils', - ); - return { - context: { - AccountsController: { - getNextAvailableAccountName: jest.fn(), - }, - KeyringController: { - state: { - keyrings: [ - { - accounts: [mockAddress1], - }, - ], - }, - }, - }, - }; -}); - -const onConfirm = jest.fn(); -const onReject = jest.fn(); - -// TODO: Replace "any" with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { - ( - useApprovalRequest as jest.MockedFn - ).mockReturnValue({ - approvalRequest, - onConfirm, - onReject, - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); -}; - -describe('SnapAccountCustomNameApproval', () => { - const getNextAvailableAccountNameMock = ( - Engine.context.AccountsController.getNextAvailableAccountName as jest.Mock - ).mockImplementation(() => 'Snap Account 3'); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const initialState: DeepPartial = { - engine: { - backgroundState: { - AccountsController: { - ...MOCK_ACCOUNTS_CONTROLLER_STATE, - }, - KeyringController: { - keyrings: [ - { - accounts: [MOCK_ADDRESS_1], - }, - ], - }, - }, - }, - }; - - it('renders correctly when approvalRequest is for showNameSnapAccount', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'New Account', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const approvalModal = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_APPROVAL); - expect(approvalModal).toBeDefined(); - }); - - it('initializes accountName with snapSuggestedAccountName when provided and name is not taken', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'Unique Account', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const input = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_INPUT); - expect(input.props.value).toBe('Unique Account'); - }); - - it('increments suffix when snapSuggestedAccountName is taken', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'Account 1', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const input = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_INPUT); - expect(input.props.value).toBe('Account 1 2'); - }); - - it('initializes accountName with next available account name when snapSuggestedAccountName is not provided', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: {}, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - getNextAvailableAccountNameMock.mockReturnValue('Snap Account 3'); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - expect(getNextAvailableAccountNameMock).toHaveBeenCalledWith( - KeyringTypes.snap, - ); - - const input = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_INPUT); - expect(input.props.value).toBe('Snap Account 3'); - }); - - it('shows error message and disables "Add Account" button when name is taken', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'Unique Account', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId, getByText } = renderWithProvider( - , - { state: initialState }, - ); - - const input = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_INPUT); - fireEvent.changeText(input, 'Account 2'); - - // Check that error message is displayed - expect(getByText('This account name already exists')).toBeDefined(); - - const addButton = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON); - expect(addButton.props.disabled).toBe(true); - }); - - it('calls onConfirm with account name when "Add Account" button is pressed and name is not taken', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'Unique Account', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const addButton = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_ADD_ACCOUNT_BUTTON); - fireEvent.press(addButton); - - expect(onConfirm).toHaveBeenCalledWith(undefined, { - success: true, - name: 'Unique Account', - }); - }); - - it('calls onReject when "Cancel" button is pressed', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: 'Unique Account', - }, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { getByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const cancelButton = getByTestId(SNAP_ACCOUNT_CUSTOM_NAME_CANCEL_BUTTON); - fireEvent.press(cancelButton); - - expect(onReject).toHaveBeenCalled(); - }); - - it('does not render when approvalRequest type is not showNameSnapAccount', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockApprovalRequestData: ApprovalRequest = { - id: '1', - origin: 'metamask', - time: Date.now(), - type: 'some_other_type', - requestData: {}, - requestState: null, - expectsResult: false, - }; - mockApprovalRequest(mockApprovalRequestData); - - const { queryByTestId } = renderWithProvider( - , - { state: initialState }, - ); - - const approvalModal = queryByTestId(SNAP_ACCOUNT_CUSTOM_NAME_APPROVAL); - expect(approvalModal).toBeNull(); - }); -}); diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 0ad4fa844e64..32ec40e72030 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -117,6 +117,7 @@ import OnboardingGeneralSettings from '../../Views/OnboardingSuccess/OnboardingG import OnboardingAssetsSettings from '../../Views/OnboardingSuccess/OnboardingAssetsSettings'; import OnboardingSecuritySettings from '../../Views/OnboardingSuccess/OnboardingSecuritySettings'; import BasicFunctionalityModal from '../../UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal'; +import SmartTransactionsOptInModal from '../../Views/SmartTransactionsOptInModal/SmartTranactionsOptInModal'; import ProfileSyncingModal from '../../UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal'; import ResetNotificationsModal from '../../UI/Notification/ResetNotificationsModal'; import NFTAutoDetectionModal from '../../../../app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal'; @@ -388,6 +389,10 @@ const RootModalFlow = () => ( name={Routes.MODAL.MODAL_MANDATORY} component={ModalMandatory} /> + { { ///: END:ONLY_INCLUDE_IF } - { - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - } - - { - ///: END:ONLY_INCLUDE_IF - } ); }; diff --git a/app/components/UI/AssetOverview/AssetOverview.test.tsx b/app/components/UI/AssetOverview/AssetOverview.test.tsx index 84b3363aef1c..039d04128489 100644 --- a/app/components/UI/AssetOverview/AssetOverview.test.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.test.tsx @@ -133,7 +133,7 @@ describe('AssetOverview', () => { expect(navigation.navigate).toHaveBeenCalledWith('Swaps', { params: { sourcePage: 'MainView', - sourceToken: asset.address, + sourceToken: '0x0000000000000000000000000000000000000000', }, screen: 'SwapsAmountView', }); diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 48b455adc8ef..995d61584c03 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -45,7 +45,7 @@ import Routes from '../../../constants/navigation/Routes'; import TokenDetails from './TokenDetails'; import { RootState } from '../../../reducers'; import useGoToBridge from '../Bridge/utils/useGoToBridge'; -import SwapsController from '@metamask/swaps-controller'; +import { swapsUtils } from '@metamask/swaps-controller'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { getDecimalChainId } from '../../../util/networks'; import { useMetrics } from '../../../components/hooks/useMetrics'; @@ -96,12 +96,12 @@ const AssetOverview: React.FC = ({ const dispatch = useDispatch(); useEffect(() => { - const { SwapsController: SwapsControllerFromEngine } = Engine.context as { - SwapsController: SwapsController; - }; + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { SwapsController } = Engine.context as { SwapsController: any }; const fetchTokenWithCache = async () => { try { - await SwapsControllerFromEngine.fetchTokenWithCache(); + await SwapsController.fetchTokenWithCache(); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { @@ -134,7 +134,7 @@ const AssetOverview: React.FC = ({ navigation.navigate('Swaps', { screen: 'SwapsAmountView', params: { - sourceToken: asset.address, + sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, sourcePage: 'MainView', }, }); diff --git a/app/components/UI/Name/Name.styles.ts b/app/components/UI/Name/Name.styles.ts index 5ba26f7c8f0e..41dd02baac82 100644 --- a/app/components/UI/Name/Name.styles.ts +++ b/app/components/UI/Name/Name.styles.ts @@ -1,4 +1,4 @@ -import { StyleSheet, TextStyle, ViewStyle, ImageStyle } from 'react-native'; +import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; import { Theme } from '../../../util/theme/models'; import { DisplayNameVariant } from '../../hooks/DisplayName/useDisplayName'; @@ -45,16 +45,9 @@ const styleSheet = (params: { flexShrink: 1, }; - const imageStyle: ImageStyle = { - borderRadius: 8, - height: 16, - width: 16, - }; - return StyleSheet.create({ base: baseStyle, label: labelStyle, - image: imageStyle, }); }; diff --git a/app/components/UI/Name/Name.test.tsx b/app/components/UI/Name/Name.test.tsx index b990e09124de..504a4b44dc43 100644 --- a/app/components/UI/Name/Name.test.tsx +++ b/app/components/UI/Name/Name.test.tsx @@ -15,6 +15,11 @@ jest.mock('../../hooks/DisplayName/useDisplayName', () => ({ default: jest.fn(), })); +jest.mock('../Identicon', () => ({ + __esModule: true, + default: () => 'Identicon', +})); + const UNKNOWN_ADDRESS_CHECKSUMMED = '0x299007B3F9E23B8d432D5f545F8a4a2B3E9A5B4e'; const EXPECTED_UNKNOWN_ADDRESS_CHECKSUMMED = '0x29900...A5B4e'; @@ -77,24 +82,5 @@ describe('Name', () => { expect(wrapper.getByText(KNOWN_NAME_MOCK)).toBeTruthy(); expect(wrapper).toMatchSnapshot(); }); - - it('should render image', () => { - mockUseDisplayName.mockReturnValue({ - variant: DisplayNameVariant.Recognized, - name: KNOWN_NAME_MOCK, - image: 'https://example.com/image.png', - }); - - const wrapper = render( - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); }); }); diff --git a/app/components/UI/Name/Name.tsx b/app/components/UI/Name/Name.tsx index 9c554d8759ae..17b01ea4656d 100644 --- a/app/components/UI/Name/Name.tsx +++ b/app/components/UI/Name/Name.tsx @@ -57,7 +57,7 @@ const Name: React.FC = ({ throw new Error('Unsupported NameType: ' + type); } - const { image, name, variant } = useDisplayName({ + const displayName = useDisplayName({ preferContractSymbol, type, value, @@ -65,23 +65,18 @@ const Name: React.FC = ({ }); const { styles } = useStyles(styleSheet, { - displayNameVariant: variant, + displayNameVariant: displayName.variant, }); - if (variant === DisplayNameVariant.Unknown) { + if (displayName.variant === DisplayNameVariant.Unknown) { return ; } return ( - - - {name} + + + {displayName.name} ); diff --git a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap index df088e79c43a..505458d92e83 100644 --- a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap +++ b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap @@ -1,104 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Name recognized address should render image 1`] = ` - - - - - - - - - Known name - - -`; - exports[`Name recognized address should return name 1`] = ` - - - - - - + Identicon { ); } - // If the current view is for Sell the amount (crypto) is displayed as is - let displayAmount = `${amount} ${selectedAsset?.symbol}`; - - // If the current ivew is for Buy we will format the amount - if (isBuy) { - // Split the amount to detect if it has decimals - const splitAmount = amount.split(/(\.)|(,)/); - // If the splitAmount array has more than 1 element it means that the amount has decimals - // For example: - // 100.50 -> splitAmount = ['100', '.', undefined, '50'] - // 100,50 -> splitAmount = ['100', undefined, ',', '50'] - // Note: this help us capture the input separator (dot or comma) - const hasDecimalsSplit = splitAmount.length > 1; - - displayAmount = - isBuy && amountFocused - ? // If the amount is focused (being edited) the amount integer part will be shown in groups separated by spaces - `${formatAmount(Math.trunc(amountNumber), true)}${ - // If the amount has decimals the decimal part will be shown - // using the separator and the decimal part - // Note, the decimal part will be displayed even if it is being typed (ends with a separator or 0) - hasDecimalsSplit - ? `${splitAmount[1] ?? splitAmount[2] ?? ''}${ - splitAmount[3] ?? '' - }` - : '' - }` - : // If the amount is not focused it will be fully formatted - formatAmount(amountNumber); - } + const displayAmount = isBuy + ? formatAmount(amountNumber) + : `${amount} ${selectedAsset?.symbol}`; let quickAmounts: QuickAmount[] = []; diff --git a/app/components/UI/Ramp/utils/index.ts b/app/components/UI/Ramp/utils/index.ts index f53d2625c091..d4ad8409ae93 100644 --- a/app/components/UI/Ramp/utils/index.ts +++ b/app/components/UI/Ramp/utils/index.ts @@ -115,17 +115,9 @@ export const formatId = (id: string) => { return id.startsWith('/') ? id : '/' + id; }; -export function formatAmount(amount: number, useParts = false) { +export function formatAmount(amount: number) { try { - if (Intl?.NumberFormat) { - if (useParts) { - return new Intl.NumberFormat() - .formatToParts(amount) - .map(({ type, value }) => (type === 'integer' ? value : '')) - .join(' '); - } - return new Intl.NumberFormat().format(amount); - } + if (Intl?.NumberFormat) return new Intl.NumberFormat().format(amount); return String(amount); } catch (e) { return String(amount); diff --git a/app/components/UI/Tokens/index.test.tsx b/app/components/UI/Tokens/index.test.tsx index 12cef3113410..401c3c8ed9d0 100644 --- a/app/components/UI/Tokens/index.test.tsx +++ b/app/components/UI/Tokens/index.test.tsx @@ -40,7 +40,7 @@ jest.mock('../../../core/Engine', () => ({ updateExchangeRate: jest.fn(() => Promise.resolve()), }, TokenRatesController: { - updateExchangeRatesByChainId: jest.fn(() => Promise.resolve()), + updateExchangeRates: jest.fn(() => Promise.resolve()), }, NetworkController: { getNetworkClientById: () => ({ @@ -359,7 +359,7 @@ describe('Tokens', () => { Engine.context.CurrencyRateController.updateExchangeRate, ).toHaveBeenCalled(); expect( - Engine.context.TokenRatesController.updateExchangeRatesByChainId, + Engine.context.TokenRatesController.updateExchangeRates, ).toHaveBeenCalled(); }); }); diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index b439310a21a2..a19a8cbaa92b 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -13,7 +13,7 @@ import { selectChainId, selectNetworkConfigurations, } from '../../../selectors/networkController'; -import { getDecimalChainId, isPortfolioViewEnabled } from '../../../util/networks'; +import { getDecimalChainId } from '../../../util/networks'; import { isZero } from '../../../util/lodash'; import createStyles from './styles'; import { TokenList } from './TokenList'; @@ -41,7 +41,6 @@ import { import ButtonBase from '../../../component-library/components/Buttons/Button/foundation/ButtonBase'; import { selectNetworkName } from '../../../selectors/networkInfos'; import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon'; -import { Hex } from '@metamask/utils'; // this will be imported from TokenRatesController when it is exported from there // PR: https://github.com/MetaMask/core/pull/4622 @@ -181,24 +180,10 @@ const Tokens: React.FC = ({ tokens }) => { TokenRatesController, } = Engine.context; const actions = [ - TokenDetectionController.detectTokens({ - chainIds: isPortfolioViewEnabled - ? Object.keys(networkConfigurationsByChainId) as Hex[] - : [chainId] - }), + TokenDetectionController.detectTokens(), AccountTrackerController.refresh(), CurrencyRateController.updateExchangeRate(nativeCurrencies), - ...(isPortfolioViewEnabled - ? Object.values(networkConfigurationsByChainId) - : [networkConfigurationsByChainId[chainId]] - ).map((network) => - TokenRatesController.updateExchangeRatesByChainId( - { - chainId: network.chainId, - nativeCurrency: network.nativeCurrency, - }, - ), - ) + TokenRatesController.updateExchangeRates(), ]; await Promise.all(actions).catch((error) => { Logger.error(error, 'Error while refreshing tokens'); @@ -254,13 +239,15 @@ const Tokens: React.FC = ({ tokens }) => { const onActionSheetPress = (index: number) => index === 0 ? removeToken() : null; + const isTokenFilterEnabled = process.env.PORTFOLIO_VIEW === 'true'; + return ( - {isPortfolioViewEnabled ? ( + {isTokenFilterEnabled ? ( { const onDeviceSelected = jest.fn(); const navigateMock = { - navigate: jest.fn().mockImplementation(() => {onDeviceSelected(device2);}), + navigate: jest.fn().mockImplementation(() => {onDeviceSelected(device2)}), } as unknown as NavigationProp; jest.mocked(useNavigation).mockReturnValue(navigateMock); diff --git a/app/components/Views/LedgerConnect/index.styles.ts b/app/components/Views/LedgerConnect/index.styles.ts index 3c962aad14c3..81db5332f03f 100644 --- a/app/components/Views/LedgerConnect/index.styles.ts +++ b/app/components/Views/LedgerConnect/index.styles.ts @@ -50,10 +50,6 @@ const createStyles = (colors: Colors) => flex: 1, marginTop: Device.getDeviceHeight() * 0.025, }, - bodyContainerWhithErrorMessage: { - flex: 1, - marginTop: Device.getDeviceHeight() * 0.01, - }, textContainer: { marginTop: Device.getDeviceHeight() * 0.05, }, diff --git a/app/components/Views/LedgerConnect/index.test.tsx b/app/components/Views/LedgerConnect/index.test.tsx index 285265f13732..8062817d5ce5 100644 --- a/app/components/Views/LedgerConnect/index.test.tsx +++ b/app/components/Views/LedgerConnect/index.test.tsx @@ -6,7 +6,7 @@ import useBluetooth from '../../hooks/Ledger/useBluetooth'; import useBluetoothDevices, { BluetoothDevice, } from '../../hooks/Ledger/useBluetoothDevices'; -import { fireEvent, waitFor } from '@testing-library/react-native'; +import { fireEvent } from '@testing-library/react-native'; import { useNavigation, NavigationProp, @@ -35,6 +35,9 @@ interface UseBluetoothDevicesHook { deviceScanError: boolean; } +jest.mock('../../hooks/useBluetoothPermissions'); +jest.mock('../../hooks/Ledger/useBluetooth'); +jest.mock('../../hooks/Ledger/useBluetoothDevices'); jest.mock('../../hooks/Ledger/useLedgerBluetooth'); jest.mock('@react-navigation/native', () => ({ ...jest.requireActual('@react-navigation/native'), @@ -59,11 +62,6 @@ jest.mock('../../../util/device', () => ({ getDeviceHeight: jest.fn(), })); -jest.mock('../../../core/Ledger/Ledger', () => ({ - ...jest.requireActual('../../../core/Ledger/Ledger'), - getDeviceId: jest.fn().mockResolvedValue('device-id'), -})); - jest.mock('../../../core/Engine', () => ({ context: { KeyringController: { @@ -368,23 +366,4 @@ describe('LedgerConnect', () => { expect(ledgerLogicToRun).toHaveBeenCalled(); }); - - it('shows error message about multiple devices support', async () => { - isSendingLedgerCommands = true; - isAppLaunchConfirmationNeeded = false; - const { getByTestId } = renderWithProvider( - , - ); - await waitFor(() => { - expect(getByTestId('multiple-devices-error-message')).toBeDefined(); - }); - }); }); diff --git a/app/components/Views/LedgerConnect/index.tsx b/app/components/Views/LedgerConnect/index.tsx index 3acd1c4e548e..1ab44858b5a1 100644 --- a/app/components/Views/LedgerConnect/index.tsx +++ b/app/components/Views/LedgerConnect/index.tsx @@ -34,11 +34,7 @@ import { getSystemVersion } from 'react-native-device-info'; import { LedgerCommunicationErrors } from '../../../core/Ledger/ledgerErrors'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import createStyles from './index.styles'; -import { - BluetoothDevice, - BluetoothInterface, -} from '../../hooks/Ledger/useBluetoothDevices'; -import { getDeviceId } from '../../../core/Ledger/Ledger'; +import { BluetoothInterface } from '../../hooks/Ledger/useBluetoothDevices'; interface LedgerConnectProps { onConnectLedger: () => void; @@ -66,9 +62,9 @@ const LedgerConnect = ({ const styles = useMemo(() => createStyles(theme.colors), [theme]); const [errorDetail, setErrorDetails] = useState(); const [loading, setLoading] = useState(false); - const [hasMatchingDeviceId, setHasMatchingDeviceId] = useState(true); const [retryTimes, setRetryTimes] = useState(0); const dispatch = useDispatch(); + const deviceOSVersion = Number(getSystemVersion()) || 0; useEffect(() => { @@ -84,20 +80,6 @@ const LedgerConnect = ({ }); }; - const onDeviceSelected = (currentDevice: BluetoothDevice | undefined) => { - const getStoredDeviceId = async () => { - const storedDeviceId = await getDeviceId(); - const isMatchingDeviceId = - !storedDeviceId || currentDevice?.id === storedDeviceId; - setHasMatchingDeviceId(isMatchingDeviceId); - - if (isMatchingDeviceId) { - setSelectedDevice(currentDevice); - } - }; - getStoredDeviceId(); - }; - const handleErrorWithRetry = (errorTitle: string, errorSubtitle: string) => { setErrorDetails({ errorTitle, @@ -129,11 +111,6 @@ const LedgerConnect = ({ }); }; - const getStylesWithMultipleDevicesErrorMessage = () => - hasMatchingDeviceId - ? styles.bodyContainer - : styles.bodyContainerWhithErrorMessage; - useEffect(() => { if (ledgerError) { setLoading(false); @@ -277,16 +254,13 @@ const LedgerConnect = ({ {strings('ledger.open_eth_app_message_two')} )} - {!hasMatchingDeviceId && ( - - {strings('ledger.multiple_devices_error_message')} - - )} - + {!isAppLaunchConfirmationNeeded ? ( + setSelectedDevice(ledgerDevice) + } onScanningErrorStateChanged={(error) => setErrorDetails(error)} ledgerError={ledgerError} /> @@ -297,9 +271,7 @@ const LedgerConnect = ({ type="confirm" onPress={connectLedger} testID={'add-network-button'} - disabled={ - !hasMatchingDeviceId || loading || isSendingLedgerCommands - } + disabled={loading || isSendingLedgerCommands} > {loading || isSendingLedgerCommands ? ( diff --git a/app/components/Views/Settings/AdvancedSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/AdvancedSettings/__snapshots__/index.test.tsx.snap index 75f86913fe86..1f1923e0c312 100644 --- a/app/components/Views/Settings/AdvancedSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/AdvancedSettings/__snapshots__/index.test.tsx.snap @@ -239,7 +239,7 @@ exports[`AdvancedSettings should render correctly 1`] = ` } thumbTintColor="#ffffff" tintColor="#bbc0c566" - value={true} + value={false} /> diff --git a/app/components/Views/Settings/AdvancedSettings/index.test.tsx b/app/components/Views/Settings/AdvancedSettings/index.test.tsx index 7c71f46c2b0c..0f0bf1629901 100644 --- a/app/components/Views/Settings/AdvancedSettings/index.test.tsx +++ b/app/components/Views/Settings/AdvancedSettings/index.test.tsx @@ -73,7 +73,7 @@ describe('AdvancedSettings', () => { Device.isIos = jest.fn().mockReturnValue(true); Device.isAndroid = jest.fn().mockReturnValue(false); - it('should render smart transactions opt in switch on by default', async () => { + it('should render smart transactions opt in switch off by default', async () => { const { findByLabelText } = renderWithProvider( { const switchElement = await findByLabelText( strings('app_settings.smart_transactions_opt_in_heading'), ); - expect(switchElement.props.value).toBe(true); + expect(switchElement.props.value).toBe(false); }); it('should update smartTransactionsOptInStatus when smart transactions opt in is pressed', async () => { const { findByLabelText } = renderWithProvider( @@ -102,9 +102,9 @@ describe('AdvancedSettings', () => { strings('app_settings.smart_transactions_opt_in_heading'), ); - fireEvent(switchElement, 'onValueChange', false); + fireEvent(switchElement, 'onValueChange', true); - expect(mockSetSmartTransactionsOptInStatus).toBeCalledWith(false); + expect(mockSetSmartTransactionsOptInStatus).toBeCalledWith(true); }); }); }); diff --git a/app/components/Views/SmartTransactionsOptInModal/SmartTranactionsOptInModal.tsx b/app/components/Views/SmartTransactionsOptInModal/SmartTranactionsOptInModal.tsx new file mode 100644 index 000000000000..5c78bdb40c96 --- /dev/null +++ b/app/components/Views/SmartTransactionsOptInModal/SmartTranactionsOptInModal.tsx @@ -0,0 +1,298 @@ +import React, { useRef } from 'react'; +import { + StyleSheet, + View, + ScrollView, + Linking, + ImageBackground, +} from 'react-native'; +import { strings } from '../../../../locales/i18n'; +import Device from '../../../util/device'; +import StorageWrapper from '../../../store/storage-wrapper'; +import { CURRENT_APP_VERSION } from '../../../constants/storage'; +import { useTheme } from '../../../util/theme'; +import Text, { + TextColor, + TextVariant, +} from '../../../component-library/components/Texts/Text'; +import Icon, { + IconName, + IconSize, +} from '../../../component-library/components/Icons/Icon'; +import ReusableModal, { ReusableModalRef } from '../../UI/ReusableModal'; +import { Colors } from '../../../util/theme/models'; +import { SmartTransactionsOptInModalSelectorsIDs } from '../../../../e2e/selectors/Modals/SmartTransactionsOptInModal.selectors'; +import Engine from '../../../core/Engine'; +import Button, { + ButtonVariants, +} from '../../../component-library/components/Buttons/Button'; +import AppConstants from '../../../core/AppConstants'; +import backgroundImage from '../../../images/smart-transactions-opt-in-bg.png'; +import { MetaMetricsEvents, useMetrics } from '../../hooks/useMetrics'; +import { useDispatch } from 'react-redux'; +import { updateOptInModalAppVersionSeen } from '../../../core/redux/slices/smartTransactions'; + +const MODAL_MARGIN = 24; +const MODAL_PADDING = 24; +const screenWidth = Device.getDeviceWidth(); +const screenHeight = Device.getDeviceHeight(); +const itemWidth = screenWidth - MODAL_MARGIN * 2; +const maxItemHeight = screenHeight - 200; + +const createStyles = (colors: Colors) => + StyleSheet.create({ + scroll: { + maxHeight: maxItemHeight, + }, + content: { + gap: 16, + paddingHorizontal: MODAL_PADDING, + }, + buttons: { + gap: 10, + justifyContent: 'center', + }, + button: { + width: '100%', + textAlign: 'center', + }, + secondaryButtonText: { + color: colors.text.alternative, + }, + header: { + alignItems: 'center', + }, + descriptions: { + gap: 16, + }, + screen: { justifyContent: 'center', alignItems: 'center' }, + modal: { + backgroundColor: colors.background.default, + borderRadius: 10, + marginHorizontal: MODAL_MARGIN, + }, + bodyContainer: { + width: itemWidth, + paddingVertical: 16, + paddingBottom: 16, + }, + benefits: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 8, + }, + benefit: { + width: '33%', + gap: 4, + alignItems: 'center', + }, + benefitIcon: { + width: 35, + height: 35, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 50, + backgroundColor: colors.primary.muted, + }, + benefitText: { + textAlign: 'center', + }, + backgroundImage: { + gap: 16, + height: 140, + justifyContent: 'center', + }, + }); + +interface Props { + iconName: IconName; + text: string[]; +} +const Benefit = ({ iconName, text }: Props) => { + const { colors } = useTheme(); + const styles = createStyles(colors); + + return ( + + + + + {text.map((t) => ( + + {t} + + ))} + + + ); +}; + +const SmartTransactionsOptInModal = () => { + const modalRef = useRef(null); + const { colors } = useTheme(); + const { trackEvent } = useMetrics(); + const dispatch = useDispatch(); + + const styles = createStyles(colors); + + const hasOptedIn = useRef(null); + + const dismissModal = async () => { + modalRef.current?.dismissModal(); + }; + + const markOptInModalAsSeen = async () => { + const version = await StorageWrapper.getItem(CURRENT_APP_VERSION); + dispatch(updateOptInModalAppVersionSeen(version)); + }; + + const optIn = async () => { + Engine.context.PreferencesController.setSmartTransactionsOptInStatus(true); + trackEvent(MetaMetricsEvents.SMART_TRANSACTION_OPT_IN, { + stx_opt_in: true, + location: 'SmartTransactionsOptInModal', + }); + hasOptedIn.current = true; + await markOptInModalAsSeen(); + dismissModal(); + }; + + const optOut = async () => { + Engine.context.PreferencesController.setSmartTransactionsOptInStatus(false); + trackEvent(MetaMetricsEvents.SMART_TRANSACTION_OPT_IN, { + stx_opt_in: false, + location: 'SmartTransactionsOptInModal', + }); + hasOptedIn.current = false; + await markOptInModalAsSeen(); + dismissModal(); + }; + + const handleDismiss = async () => { + // Opt out of STX if no prior decision made. + if (hasOptedIn.current === null) { + optOut(); + } + }; + + const renderHeader = () => ( + + + {strings('whats_new.stx.header')} + + + ); + + const renderBenefits = () => ( + + + + + + ); + + const renderDescriptions = () => ( + + {strings('whats_new.stx.description_1')} + + {strings('whats_new.stx.description_2')}{' '} + { + Linking.openURL(AppConstants.URLS.SMART_TXS); + }} + > + {strings('whats_new.stx.learn_more')} + + + + ); + + const renderPrimaryButton = () => ( + + ); + + const renderSecondaryButton = () => ( + + ); + + return ( + + + + + {renderHeader()} + {renderBenefits()} + + + {/* Content */} + + + {renderDescriptions()} + + + {renderPrimaryButton()} + {renderSecondaryButton()} + + + + + + + ); +}; + +export default SmartTransactionsOptInModal; diff --git a/app/components/Views/SmartTransactionsOptInModal/SmartTransactionsOptInModal.test.tsx b/app/components/Views/SmartTransactionsOptInModal/SmartTransactionsOptInModal.test.tsx new file mode 100644 index 000000000000..02664a58ebfb --- /dev/null +++ b/app/components/Views/SmartTransactionsOptInModal/SmartTransactionsOptInModal.test.tsx @@ -0,0 +1,149 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import React from 'react'; +import { fireEvent } from '@testing-library/react-native'; +import SmartTransactionsOptInModal from './SmartTranactionsOptInModal'; +import renderWithProvider from '../../../util/test/renderWithProvider'; +import { backgroundState } from '../../../util/test/initial-root-state'; +import { strings } from '../../../../locales/i18n'; +import Engine from '../../../core/Engine'; +import { shouldShowWhatsNewModal } from '../../../util/onboarding'; +import { updateOptInModalAppVersionSeen } from '../../../core/redux/slices/smartTransactions'; + +const mockNavigate = jest.fn(); +jest.mock('@react-navigation/native', () => { + const actualReactNavigation = jest.requireActual('@react-navigation/native'); + return { + ...actualReactNavigation, + useNavigation: () => ({ + navigate: mockNavigate, + goBack: jest.fn(), + }), + }; +}); + +jest.mock('../../../core/Engine', () => ({ + context: { + PreferencesController: { + setSmartTransactionsOptInStatus: jest.fn(), + }, + }, +})); + +const VERSION = '1.0.0'; +jest.mock('../../../store/storage-wrapper', () => ({ + getItem: jest.fn(() => VERSION), + setItem: jest.fn(), + removeItem: jest.fn(), +})); + +jest.mock('../../../util/onboarding', () => ({ + shouldShowWhatsNewModal: jest.fn(), +})); + +jest.mock('../../../core/redux/slices/smartTransactions', () => ({ + updateOptInModalAppVersionSeen: jest.fn(() => ({ type: 'hello' })), +})); + +const initialState = { + engine: { + backgroundState, + }, +}; + +function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +describe('SmartTransactionsOptInModal', () => { + afterEach(() => { + mockNavigate.mockReset(); + }); + + it('should render properly', () => { + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const header = getByText(strings('whats_new.stx.header')); + expect(header).toBeDefined(); + + const description1 = getByText(strings('whats_new.stx.description_1')); + expect(description1).toBeDefined(); + + const description2 = getByText(strings('whats_new.stx.description_2'), { + exact: false, + }); + expect(description2).toBeDefined(); + + const primaryButton = getByText(strings('whats_new.stx.primary_button')); + expect(primaryButton).toBeDefined(); + + const secondaryButton = getByText(strings('whats_new.stx.no_thanks')); + expect(secondaryButton).toBeDefined(); + }); + + it('should opt user in when primary button is pressed', () => { + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const primaryButton = getByText(strings('whats_new.stx.primary_button')); + fireEvent.press(primaryButton); + + expect( + Engine.context.PreferencesController.setSmartTransactionsOptInStatus, + ).toHaveBeenCalledWith(true); + }); + + it('opts user out when secondary button is pressed', async () => { + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const secondaryButton = getByText(strings('whats_new.stx.no_thanks')); + fireEvent.press(secondaryButton); + + expect( + Engine.context.PreferencesController.setSmartTransactionsOptInStatus, + ).toHaveBeenCalledWith(false); + }); + + it('should update last app version seen on primary button press', () => { + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const primaryButton = getByText(strings('whats_new.stx.primary_button')); + fireEvent.press(primaryButton); + + expect(updateOptInModalAppVersionSeen).toHaveBeenCalledWith(VERSION); + }); + + it('should update last app version seen on secondary button press', () => { + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const secondaryButton = getByText(strings('whats_new.stx.no_thanks')); + fireEvent.press(secondaryButton); + + expect(updateOptInModalAppVersionSeen).toHaveBeenCalledWith(VERSION); + }); + + it("should not navigate to What's New modal", async () => { + (shouldShowWhatsNewModal as jest.Mock).mockImplementation( + async () => false, + ); + + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const primaryButton = getByText(strings('whats_new.stx.primary_button')); + fireEvent.press(primaryButton); + + await wait(10); + + expect(mockNavigate).not.toHaveBeenCalled(); + }); +}); diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index c6c416aaca47..82b993b38a9a 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -4,6 +4,7 @@ import { StyleSheet, View, TextStyle, + InteractionManager, Linking, } from 'react-native'; import type { Theme } from '@metamask/design-tokens'; @@ -35,6 +36,8 @@ import { getTicker } from '../../../util/transactions'; import OnboardingWizard from '../../UI/OnboardingWizard'; import ErrorBoundary from '../ErrorBoundary'; import { useTheme } from '../../../util/theme'; +import { shouldShowSmartTransactionsOptInModal } from '../../../util/onboarding'; +import Logger from '../../../util/Logger'; import Routes from '../../../constants/navigation/Routes'; import { getDecimalChainId, @@ -333,7 +336,7 @@ const Wallet = ({ }); /** - * Check to see if we need to show What's New modal + * Check to see if we need to show What's New modal and Smart Transactions Opt In modal */ useEffect(() => { const networkOnboarded = getIsNetworkOnboarded( @@ -348,6 +351,36 @@ const Wallet = ({ // Do not check since it will conflict with the onboarding wizard and/or network onboarding return; } + + // Show STX opt in modal before What's New modal + // Fired on the first load of the wallet and also on network switch + const checkSmartTransactionsOptInModal = async () => { + try { + const accountHasZeroBalance = hexToBN( + accountBalanceByChainId?.balance || '0x0', + ).isZero(); + const shouldShowStxOptInModal = + await shouldShowSmartTransactionsOptInModal( + providerConfig.chainId, + providerConfig.rpcUrl, + accountHasZeroBalance, + ); + if (shouldShowStxOptInModal) { + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.MODAL.SMART_TRANSACTIONS_OPT_IN, + }); + } + } catch (error) { + Logger.log( + error, + 'Error while checking Smart Tranasctions Opt In modal!', + ); + } + }; + + InteractionManager.runAfterInteractions(() => { + checkSmartTransactionsOptInModal(); + }); }, [ wizardStep, navigation, diff --git a/app/components/Views/confirmations/Confirm/Confirm.test.tsx b/app/components/Views/confirmations/Confirm/Confirm.test.tsx index 21bdbbabd2bc..af02b70c2fa0 100644 --- a/app/components/Views/confirmations/Confirm/Confirm.test.tsx +++ b/app/components/Views/confirmations/Confirm/Confirm.test.tsx @@ -1,10 +1,7 @@ import React from 'react'; import renderWithProvider from '../../../../util/test/renderWithProvider'; -import { - personalSignatureConfirmationState, - typedSignV1ConfirmationState, -} from '../../../../util/test/confirm-data-helpers'; +import { personalSignatureConfirmationState } from '../../../../util/test/confirm-data-helpers'; import Confirm from './index'; describe('Confirm', () => { @@ -14,11 +11,4 @@ describe('Confirm', () => { }); expect(container).toMatchSnapshot(); }); - - it('should match snapshot for typed sign v1', async () => { - const container = renderWithProvider(, { - state: typedSignV1ConfirmationState, - }); - expect(container).toMatchSnapshot(); - }); }); diff --git a/app/components/Views/confirmations/Confirm/__snapshots__/Confirm.test.tsx.snap b/app/components/Views/confirmations/Confirm/__snapshots__/Confirm.test.tsx.snap index 9cd813772a12..c45d1a3fcbf4 100644 --- a/app/components/Views/confirmations/Confirm/__snapshots__/Confirm.test.tsx.snap +++ b/app/components/Views/confirmations/Confirm/__snapshots__/Confirm.test.tsx.snap @@ -806,821 +806,3 @@ exports[`Confirm should match snapshot for personal sign 1`] = ` `; - -exports[`Confirm should match snapshot for typed sign v1 1`] = ` - - - - - - - - Signature request - - - - - - - - - - - - - - - - - - - - - - 0x935E...5477 - - - Ethereum Mainnet - - - - - - - - - - - Estimated changes - - - - - - - - - - You’re signing into a site and there are no predicted changes to your account. - - - - - - - - Request from - - - - - - - - - - - metamask.github.io - - - - - - - - - Message - - - [ - { - "type": "string", - "name": "Message", - "value": "Hi, Alice!" - }, - { - "type": "uint32", - "name": "A number", - "value": "1337" - } -] - - - - - - - - - - Reject - - - - - - Confirm - - - - - - -`; diff --git a/app/components/Views/confirmations/SendFlow/Confirm/index.js b/app/components/Views/confirmations/SendFlow/Confirm/index.js index a8b8d3641a28..4aae1abb75f2 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.js +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.js @@ -12,7 +12,6 @@ import { connect } from 'react-redux'; import { getSendFlowTitle } from '../../../../UI/Navbar'; import PropTypes from 'prop-types'; import Eth from '@metamask/ethjs-query'; -import { isEmpty } from 'lodash'; import { renderFromWei, renderFromTokenMinimalUnit, @@ -487,25 +486,11 @@ class Confirm extends PureComponent { const { TransactionController } = Engine.context; const transactionParams = this.prepareTransactionToSend(); - let result, transactionMeta; - try { - ({ result, transactionMeta } = await TransactionController.addTransaction( - transactionParams, - { - deviceConfirmedOn: WalletDevice.MM_MOBILE, - origin: TransactionTypes.MMM, - }, - )); - } catch (error) { - Logger.error(error, 'error while adding transaction (Confirm)'); - navigation?.dangerouslyGetParent()?.pop(); - Alert.alert( - strings('transactions.transaction_error'), - error && error.message, - [{ text: 'OK' }], - ); - return; - } + const { result, transactionMeta } = + await TransactionController.addTransaction(transactionParams, { + deviceConfirmedOn: WalletDevice.MM_MOBILE, + origin: TransactionTypes.MMM, + }); setTransactionId(transactionMeta.id); @@ -1320,7 +1305,6 @@ class Confirm extends PureComponent { EIP1559GasObject, EIP1559GasTransaction, legacyGasObject, - transactionMeta, } = this.state; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); @@ -1493,7 +1477,6 @@ class Confirm extends PureComponent { { ); }); }); - - it('should show error if transaction is not added', async () => { - jest.spyOn(Alert, 'alert'); - - Engine.context.TransactionController.addTransaction = jest - .fn() - .mockRejectedValue(new Error('Transaction not added')); - - render(Confirm); - - await flushPromises(); - - await waitFor(() => { - expect(Alert.alert).toHaveBeenCalledWith( - 'Transaction error', - 'Transaction not added', - expect.any(Array), - ); - }); - }); }); diff --git a/app/components/Views/confirmations/components/Confirm/Info/Info.tsx b/app/components/Views/confirmations/components/Confirm/Info/Info.tsx index 521f17cb11ef..9b41c523a0ef 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/Info.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/Info.tsx @@ -3,11 +3,9 @@ import React from 'react'; import useApprovalRequest from '../../../hooks/useApprovalRequest'; import PersonalSign from './PersonalSign'; -import TypedSignV1 from './TypedSignV1'; const ConfirmationInfoComponentMap = { [TransactionType.personalSign]: () => PersonalSign, - [TransactionType.signTypedData]: () => TypedSignV1, }; const Info = () => { diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message.tsx deleted file mode 100644 index b30860f4113f..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useMemo } from 'react'; -import { StyleSheet, Text } from 'react-native'; -import { hexToText } from '@metamask/controller-utils'; - -import { Theme } from '../../../../../../../util/theme/models'; -import { fontStyles } from '../../../../../../../styles/common'; -import { useStyles } from '../../../../../../../component-library/hooks'; -import { sanitizeString } from '../../../../../../../util/string'; -import useApprovalRequest from '../../../../hooks/useApprovalRequest'; -import SignatureMessageSection from '../../SignatureMessageSection'; - -const styleSheet = (params: { theme: Theme }) => { - const { theme } = params; - - return StyleSheet.create({ - messageExpanded: { - color: theme.colors.text.default, - ...fontStyles.normal, - fontSize: 14, - fontWeight: '400', - }, - }); -}; - -const Message = () => { - const { approvalRequest } = useApprovalRequest(); - const { styles } = useStyles(styleSheet, {}); - - const message = useMemo( - () => sanitizeString(hexToText(approvalRequest?.requestData?.data)), - [approvalRequest?.requestData?.data], - ); - - return ( - {message}} - copyMessageText={message} - /> - ); -}; - -export default Message; diff --git a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.styles.ts similarity index 87% rename from app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.styles.ts rename to app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.styles.ts index 559a44a656eb..773c248e396d 100644 --- a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.styles.ts @@ -1,7 +1,7 @@ import { StyleSheet } from 'react-native'; -import { Theme } from '../../../../../../util/theme/models'; -import { fontStyles } from '../../../../../../styles/common'; +import { Theme } from '../../../../../../../../util/theme/models'; +import { fontStyles } from '../../../../../../../../styles/common'; const styleSheet = (params: { theme: Theme }) => { const { theme } = params; diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.test.tsx new file mode 100644 index 000000000000..d0d0661bfaa4 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.test.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { fireEvent } from '@testing-library/react-native'; + +import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; +import { personalSignatureConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers'; +import Message from './index'; + +describe('Message', () => { + it('should match snapshot', async () => { + const container = renderWithProvider(, { + state: personalSignatureConfirmationState, + }); + expect(container).toMatchSnapshot(); + }); + + it('should show expanded view when open button is clicked', async () => { + const { getByTestId, getByText, getAllByText } = renderWithProvider( + , + { + state: personalSignatureConfirmationState, + }, + ); + expect(getAllByText('Message')).toHaveLength(1); + expect(getAllByText('Example `personal_sign` message')).toHaveLength(1); + fireEvent.press(getByText('Message')); + expect(getAllByText('Message')).toHaveLength(2); + expect(getAllByText('Example `personal_sign` message')).toHaveLength(2); + expect(getByTestId('copyButtonTestId')).toBeDefined(); + }); +}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.tsx new file mode 100644 index 000000000000..b80f2d23eed3 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/Message.tsx @@ -0,0 +1,45 @@ +import React, { useMemo } from 'react'; +import { Text, View } from 'react-native'; +import { hexToText } from '@metamask/controller-utils'; + +import { sanitizeString } from '../../../../../../../../util/string'; +import { strings } from '../../../../../../../../../locales/i18n'; +import { useStyles } from '../../../../../../../../component-library/hooks'; +import useApprovalRequest from '../../../../../hooks/useApprovalRequest'; +import ExpandableSection from '../../../../UI/ExpandableSection'; +import styleSheet from './Message.styles'; +import CopyButton from '../../../../UI/CopyButton'; + +const Message = () => { + const { approvalRequest } = useApprovalRequest(); + const { styles } = useStyles(styleSheet, {}); + + const message = useMemo( + () => sanitizeString(hexToText(approvalRequest?.requestData?.data)), + [approvalRequest?.requestData?.data], + ); + + return ( + + {strings('confirm.message')} + + {message} + + + } + expandedContent={ + + + + + {message} + + } + expandedContentTitle={strings('confirm.message')} + /> + ); +}; + +export default Message; diff --git a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/__snapshots__/SignatureMessageSection.test.tsx.snap b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/__snapshots__/Message.test.tsx.snap similarity index 92% rename from app/components/Views/confirmations/components/Confirm/SignatureMessageSection/__snapshots__/SignatureMessageSection.test.tsx.snap rename to app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/__snapshots__/Message.test.tsx.snap index 373d51084d47..3a9ef6f8132a 100644 --- a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/__snapshots__/SignatureMessageSection.test.tsx.snap +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message/__snapshots__/Message.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SignatureMessageSection should match snapshot 1`] = ` +exports[`Message should match snapshot 1`] = ` - Signature request collapsed message + Example \`personal_sign\` message { +describe('Title', () => { it('should match snapshot', async () => { - const container = renderWithProvider(, { + const container = renderWithProvider(, { state: personalSignatureConfirmationState, }); expect(container).toMatchSnapshot(); diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/PersonalSign.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/PersonalSign.tsx index 53ac21caa1f5..222f834e16f4 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/PersonalSign.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/PersonalSign.tsx @@ -5,8 +5,8 @@ import useApprovalRequest from '../../../../hooks/useApprovalRequest'; import InfoSection from '../../../UI/InfoRow/InfoSection'; import InfoRow from '../../../UI/InfoRow'; import DisplayURL from '../../../UI/InfoRow/InfoValue/DisplayURL'; -import NoChangeSimulation from '../../NoChangeSimulation'; import Message from './Message'; +import Simulation from './Simulation'; const PersonalSign = () => { const { approvalRequest } = useApprovalRequest(); @@ -17,7 +17,7 @@ const PersonalSign = () => { return ( <> - <NoChangeSimulation /> + <Simulation /> <InfoSection> <InfoRow label={strings('confirm.request_from')} diff --git a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.test.tsx similarity index 68% rename from app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx rename to app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.test.tsx index 925fb26cc9ab..71556c4803a2 100644 --- a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.test.tsx @@ -1,19 +1,19 @@ import React from 'react'; -import renderWithProvider from '../../../../../../util/test/renderWithProvider'; -import { personalSignatureConfirmationState } from '../../../../../../util/test/confirm-data-helpers'; -import NoChangeSimulation from './NoChangeSimulation'; +import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; +import { personalSignatureConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers'; +import Simulation from './index'; -describe('NoChangeSimulation', () => { +describe('Simulation', () => { it('should match snapshot', async () => { - const container = renderWithProvider(<NoChangeSimulation />, { + const container = renderWithProvider(<Simulation />, { state: personalSignatureConfirmationState, }); expect(container).toMatchSnapshot(); }); it('should return null if preference useTransactionSimulations is not enabled', async () => { - const container = renderWithProvider(<NoChangeSimulation />, { + const container = renderWithProvider(<Simulation />, { state: { engine: { backgroundState: { diff --git a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.tsx similarity index 67% rename from app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.tsx rename to app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.tsx index f17f6ac0aad3..248be3bbcad8 100644 --- a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/Simulation.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { strings } from '../../../../../../../locales/i18n'; -import { selectUseTransactionSimulations } from '../../../../../../selectors/preferencesController'; -import InfoSection from '../../UI/InfoRow/InfoSection'; -import InfoRow from '../../UI/InfoRow'; +import { strings } from '../../../../../../../../../locales/i18n'; +import { selectUseTransactionSimulations } from '../../../../../../../../selectors/preferencesController'; +import InfoSection from '../../../../UI/InfoRow/InfoSection'; +import InfoRow from '../../../../UI/InfoRow'; -const NoChangeSimulation = () => { +const Simulation = () => { const useTransactionSimulations = useSelector( selectUseTransactionSimulations, ); @@ -27,4 +27,4 @@ const NoChangeSimulation = () => { ); }; -export default NoChangeSimulation; +export default Simulation; diff --git a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/__snapshots__/NoChangeSimulation.test.tsx.snap b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/__snapshots__/Simulation.test.tsx.snap similarity index 94% rename from app/components/Views/confirmations/components/Confirm/NoChangeSimulation/__snapshots__/NoChangeSimulation.test.tsx.snap rename to app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/__snapshots__/Simulation.test.tsx.snap index 318d58b5a75f..ec9301c9d736 100644 --- a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/__snapshots__/NoChangeSimulation.test.tsx.snap +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/__snapshots__/Simulation.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NoChangeSimulation should match snapshot 1`] = ` +exports[`Simulation should match snapshot 1`] = ` <View style={ { @@ -128,4 +128,4 @@ exports[`NoChangeSimulation should match snapshot 1`] = ` </View> `; -exports[`NoChangeSimulation should return null if preference useTransactionSimulations is not enabled 1`] = `null`; +exports[`Simulation should return null if preference useTransactionSimulations is not enabled 1`] = `null`; diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/index.ts b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/index.ts new file mode 100644 index 000000000000..50cee91255fc --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Simulation/index.ts @@ -0,0 +1 @@ +export { default } from './Simulation'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/__snapshots__/PersonalSign.test.tsx.snap b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/__snapshots__/PersonalSign.test.tsx.snap index 206eacdc3ec7..863fe1c904b9 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/__snapshots__/PersonalSign.test.tsx.snap +++ b/app/components/Views/confirmations/components/Confirm/Info/PersonalSign/__snapshots__/PersonalSign.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PersonalSign should match snapshot 1`] = ` +exports[`Title should match snapshot 1`] = ` [ <View style={ diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/Message.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/Message.tsx deleted file mode 100644 index 97e126d21aaf..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/Message.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { useMemo } from 'react'; -import { Text } from 'react-native'; - -import useApprovalRequest from '../../../../hooks/useApprovalRequest'; -import SignatureMessageSection from '../../SignatureMessageSection'; - -const Message = () => { - const { approvalRequest } = useApprovalRequest(); - - const message = useMemo( - () => JSON.stringify(approvalRequest?.requestData?.data, undefined, 4), - [approvalRequest?.requestData?.data], - ); - - return ( - <SignatureMessageSection - messageCollapsed={message} - messageExpanded={<Text>{message}</Text>} - copyMessageText={message} - /> - ); -}; - -export default Message; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx deleted file mode 100644 index 6f95dadd6044..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import renderWithProvider from '../../../../../../../util/test/renderWithProvider'; -import { personalSignatureConfirmationState } from '../../../../../../../util/test/confirm-data-helpers'; -import TypedSignV1 from './index'; - -describe('TypedSignV1', () => { - it('should match snapshot', async () => { - const container = renderWithProvider(<TypedSignV1 />, { - state: personalSignatureConfirmationState, - }); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.tsx deleted file mode 100644 index e4eb92ad8b9f..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import { strings } from '../../../../../../../../locales/i18n'; -import useApprovalRequest from '../../../../hooks/useApprovalRequest'; -import InfoSection from '../../../UI/InfoRow/InfoSection'; -import InfoRow from '../../../UI/InfoRow'; -import DisplayURL from '../../../UI/InfoRow/InfoValue/DisplayURL'; -import Message from './Message'; -import NoChangeSimulation from '../../NoChangeSimulation'; - -const TypedSignV1 = () => { - const { approvalRequest } = useApprovalRequest(); - - if (!approvalRequest) { - return null; - } - - return ( - <> - <NoChangeSimulation /> - <InfoSection> - <InfoRow - label={strings('confirm.request_from')} - tooltip={strings('confirm.personal_sign_tooltip')} - > - <DisplayURL url={approvalRequest.origin} /> - </InfoRow> - </InfoSection> - <Message /> - </> - ); -}; - -export default TypedSignV1; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/__snapshots__/TypedSignV1.test.tsx.snap b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/__snapshots__/TypedSignV1.test.tsx.snap deleted file mode 100644 index 8fdc7b64b635..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/__snapshots__/TypedSignV1.test.tsx.snap +++ /dev/null @@ -1,337 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TypedSignV1 should match snapshot 1`] = ` -[ - <View - style={ - { - "backgroundColor": "#ffffff", - "borderColor": "#bbc0c566", - "borderRadius": 8, - "borderWidth": 1, - "marginBottom": 8, - "padding": 8, - } - } - > - <View - style={ - { - "display": "flex", - "flexDirection": "row", - "flexWrap": "wrap", - "justifyContent": "space-between", - "paddingBottom": 8, - "paddingHorizontal": 8, - } - } - > - <View - style={ - { - "alignItems": "center", - "display": "flex", - "flexDirection": "row", - "marginTop": 8, - } - } - > - <Text - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Bold", - "fontSize": 14, - "fontWeight": "500", - } - } - > - Estimated changes - </Text> - <View> - <TouchableOpacity - accessible={true} - activeOpacity={1} - disabled={false} - onPress={[Function]} - onPressIn={[Function]} - onPressOut={[Function]} - style={ - { - "alignItems": "center", - "borderRadius": 8, - "height": 24, - "justifyContent": "center", - "opacity": 1, - "width": 24, - } - } - testID="tooltipTestId" - > - <SvgMock - color="#9fa6ae" - height={16} - name="Info" - style={ - { - "height": 16, - "width": 16, - } - } - width={16} - /> - </TouchableOpacity> - <Modal - animationType="none" - deviceHeight={null} - deviceWidth={null} - hardwareAccelerated={false} - hideModalContentWhileAnimating={false} - onBackdropPress={[Function]} - onModalHide={[Function]} - onModalWillHide={[Function]} - onModalWillShow={[Function]} - onRequestClose={[Function]} - onSwipeComplete={[Function]} - panResponderThreshold={4} - scrollHorizontal={false} - scrollOffset={0} - scrollOffsetMax={0} - scrollTo={null} - statusBarTranslucent={false} - supportedOrientations={ - [ - "portrait", - "landscape", - ] - } - swipeDirection="down" - swipeThreshold={100} - transparent={true} - visible={false} - /> - </View> - </View> - <Text - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Regular", - "fontSize": 14, - "fontWeight": "400", - "marginTop": 8, - } - } - > - You’re signing into a site and there are no predicted changes to your account. - </Text> - </View> - </View>, - <View - style={ - { - "backgroundColor": "#ffffff", - "borderColor": "#bbc0c566", - "borderRadius": 8, - "borderWidth": 1, - "marginBottom": 8, - "padding": 8, - } - } - > - <View - style={ - { - "display": "flex", - "flexDirection": "row", - "flexWrap": "wrap", - "justifyContent": "space-between", - "paddingBottom": 8, - "paddingHorizontal": 8, - } - } - > - <View - style={ - { - "alignItems": "center", - "display": "flex", - "flexDirection": "row", - "marginTop": 8, - } - } - > - <Text - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Bold", - "fontSize": 14, - "fontWeight": "500", - } - } - > - Request from - </Text> - <View> - <TouchableOpacity - accessible={true} - activeOpacity={1} - disabled={false} - onPress={[Function]} - onPressIn={[Function]} - onPressOut={[Function]} - style={ - { - "alignItems": "center", - "borderRadius": 8, - "height": 24, - "justifyContent": "center", - "opacity": 1, - "width": 24, - } - } - testID="tooltipTestId" - > - <SvgMock - color="#9fa6ae" - height={16} - name="Info" - style={ - { - "height": 16, - "width": 16, - } - } - width={16} - /> - </TouchableOpacity> - <Modal - animationType="none" - deviceHeight={null} - deviceWidth={null} - hardwareAccelerated={false} - hideModalContentWhileAnimating={false} - onBackdropPress={[Function]} - onModalHide={[Function]} - onModalWillHide={[Function]} - onModalWillShow={[Function]} - onRequestClose={[Function]} - onSwipeComplete={[Function]} - panResponderThreshold={4} - scrollHorizontal={false} - scrollOffset={0} - scrollOffsetMax={0} - scrollTo={null} - statusBarTranslucent={false} - supportedOrientations={ - [ - "portrait", - "landscape", - ] - } - swipeDirection="down" - swipeThreshold={100} - transparent={true} - visible={false} - /> - </View> - </View> - <View - style={ - { - "alignItems": "center", - "display": "flex", - "flexDirection": "row", - } - } - > - <Text - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Regular", - "fontSize": 14, - "fontWeight": "400", - "marginTop": 8, - } - } - > - metamask.github.io - </Text> - </View> - </View> - </View>, - <TouchableOpacity - accessible={true} - activeOpacity={1} - onPress={[Function]} - onPressIn={[Function]} - onPressOut={[Function]} - > - <View - style={ - { - "alignItems": "center", - "backgroundColor": "#ffffff", - "borderColor": "#bbc0c566", - "borderRadius": 8, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "justifyContent": "space-between", - "marginBottom": 8, - "padding": 16, - } - } - > - <View - style={ - { - "display": "flex", - } - } - > - <Text - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Regular", - "fontSize": 14, - "fontWeight": "500", - "marginBottom": 4, - } - } - > - Message - </Text> - <Text - numberOfLines={1} - style={ - { - "color": "#141618", - "fontFamily": "EuclidCircularB-Regular", - "fontSize": 14, - "fontWeight": "400", - } - } - > - "0x4578616d706c652060706572736f6e616c5f7369676e60206d657373616765" - </Text> - </View> - <SvgMock - color="#9fa6ae" - height={16} - name="ArrowRight" - style={ - { - "height": 16, - "width": 16, - } - } - width={16} - /> - </View> - </TouchableOpacity>, -] -`; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/index.ts deleted file mode 100644 index e6bff3920e86..000000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TypedSignV1'; diff --git a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/index.ts b/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/index.ts deleted file mode 100644 index 4221a223fe24..000000000000 --- a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './NoChangeSimulation'; diff --git a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.test.tsx b/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.test.tsx deleted file mode 100644 index bab60229eb73..000000000000 --- a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { fireEvent } from '@testing-library/react-native'; -import { Text } from 'react-native'; - -import renderWithProvider from '../../../../../../util/test/renderWithProvider'; -import SignatureMessageSection from './SignatureMessageSection'; - -describe('SignatureMessageSection', () => { - it('should match snapshot', async () => { - const container = renderWithProvider( - <SignatureMessageSection - messageCollapsed="Signature request collapsed message" - messageExpanded={<Text>Signature request expanded message</Text>} - copyMessageText="Signature request copy message" - />, - { - state: {}, - }, - ); - expect(container).toMatchSnapshot(); - }); - - it('should show expanded view when open button is clicked', async () => { - const { getByTestId, getByText, getAllByText } = renderWithProvider( - <SignatureMessageSection - messageCollapsed="Signature request collapsed message" - messageExpanded={<Text>Signature request expanded message</Text>} - copyMessageText="Signature request copy message" - />, - { - state: {}, - }, - ); - expect(getAllByText('Message')).toHaveLength(1); - expect(getAllByText('Signature request collapsed message')).toHaveLength(1); - fireEvent.press(getByText('Message')); - expect(getAllByText('Message')).toHaveLength(2); - expect(getAllByText('Signature request expanded message')).toHaveLength(1); - expect(getByTestId('copyButtonTestId')).toBeDefined(); - }); -}); diff --git a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.tsx b/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.tsx deleted file mode 100644 index 8af9685c5658..000000000000 --- a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { ReactNode } from 'react'; -import { Text, View } from 'react-native'; - -import { strings } from '../../../../../../../locales/i18n'; -import { useStyles } from '../../../../../../component-library/hooks'; -import CopyButton from '../../UI/CopyButton'; -import ExpandableSection from '../../UI/ExpandableSection'; -import styleSheet from './SignatureMessageSection.styles'; - -interface SignatureMessageSectionProps { - messageCollapsed: string; - messageExpanded: ReactNode; - copyMessageText: string; -} - -const SignatureMessageSection = ({ - messageCollapsed, - messageExpanded, - copyMessageText, -}: SignatureMessageSectionProps) => { - const { styles } = useStyles(styleSheet, {}); - - return ( - <ExpandableSection - collapsedContent={ - <View style={styles.container}> - <Text style={styles.title}>{strings('confirm.message')}</Text> - <Text style={styles.description} numberOfLines={1}> - {messageCollapsed} - </Text> - </View> - } - expandedContent={ - <View style={styles.messageContainer}> - <View style={styles.copyButtonContainer}> - <CopyButton copyText={copyMessageText} /> - </View> - {messageExpanded} - </View> - } - expandedContentTitle={strings('confirm.message')} - /> - ); -}; - -export default SignatureMessageSection; diff --git a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/index.ts b/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/index.ts deleted file mode 100644 index b4fbdcb684cd..000000000000 --- a/app/components/Views/confirmations/components/Confirm/SignatureMessageSection/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SignatureMessageSection'; diff --git a/app/components/Views/confirmations/components/Confirm/Title/Title.tsx b/app/components/Views/confirmations/components/Confirm/Title/Title.tsx index 2f8fdf74148a..b175803af165 100644 --- a/app/components/Views/confirmations/components/Confirm/Title/Title.tsx +++ b/app/components/Views/confirmations/components/Confirm/Title/Title.tsx @@ -10,7 +10,6 @@ import styleSheet from './Title.styles'; const getTitle = (confirmationType?: string) => { switch (confirmationType) { case TransactionType.personalSign: - case TransactionType.signTypedData: return strings('confirm.title.signature'); default: return ''; diff --git a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts index 02bcd6e19105..f9e6a531a316 100644 --- a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts +++ b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts @@ -5,20 +5,14 @@ import useApprovalRequest from './useApprovalRequest'; const useConfirmationRedesignEnabled = () => { const { approvalRequest } = useApprovalRequest(); - - const { type: approvalRequestType, requestData } = approvalRequest ?? { - requestData: {}, - }; - const approvalRequestVersion = requestData?.version; + const approvalRequestType = approvalRequest?.type; const isRedesignedEnabled = useMemo( () => approvalRequestType && process.env.REDESIGNED_SIGNATURE_REQUEST === 'true' && - (approvalRequestType === ApprovalTypes.PERSONAL_SIGN || - (approvalRequestType === ApprovalTypes.ETH_SIGN_TYPED_DATA && - approvalRequestVersion === 'V1')), - [approvalRequestType, approvalRequestVersion], + approvalRequestType === ApprovalTypes.PERSONAL_SIGN, + [approvalRequestType], ); return { isRedesignedEnabled }; diff --git a/app/components/hooks/AssetPolling/AssetPollingProvider.tsx b/app/components/hooks/AssetPolling/AssetPollingProvider.tsx index 1d19d82d9fca..7c7c3eda5fa5 100644 --- a/app/components/hooks/AssetPolling/AssetPollingProvider.tsx +++ b/app/components/hooks/AssetPolling/AssetPollingProvider.tsx @@ -1,17 +1,11 @@ import React, { ReactNode } from 'react'; import useCurrencyRatePolling from './useCurrencyRatePolling'; -import useTokenRatesPolling from './useTokenRatesPolling'; -import useTokenDetectionPolling from './useTokenDetectionPolling'; -import useTokenListPolling from './useTokenListPolling'; // This provider is a step towards making controller polling fully UI based. // Eventually, individual UI components will call the use*Polling hooks to // poll and return particular data. This polls globally in the meantime. export const AssetPollingProvider = ({ children }: { children: ReactNode }) => { useCurrencyRatePolling(); - useTokenRatesPolling(); - useTokenDetectionPolling(); - useTokenListPolling(); return <>{children}</>; }; diff --git a/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts b/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts deleted file mode 100644 index 1294d23cad81..000000000000 --- a/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; -import Engine from '../../../core/Engine'; -import useTokenDetectionPolling from './useTokenDetectionPolling'; - -jest.mock('../../../core/Engine', () => ({ - context: { - TokenDetectionController: { - startPolling: jest.fn(), - stopPollingByPollingToken: jest.fn(), - }, - }, -})); - -describe('useTokenDetectionPolling', () => { - - beforeEach(() => { - jest.resetAllMocks(); - }); - - const selectedAddress = '0x1234567890abcdef'; - const selectedChainId = '0x1' as const; - - const state = { - engine: { - backgroundState: { - AccountsController: { - internalAccounts: { - selectedAccount: '1', - accounts: { - '1': { - address: selectedAddress - } - }, - }, - }, - PreferencesController: { - useTokenDetection: true, - }, - NetworkController: { - selectedNetworkClientId: 'selectedNetworkClientId', - networkConfigurationsByChainId: { - [selectedChainId]: { - chainId: selectedChainId, - rpcEndpoints: [{ - networkClientId: 'selectedNetworkClientId', - }] - }, - '0x89': {}, - }, - }, - }, - }, - }; - - it('Should poll by current chain ids/address, and stop polling on dismount', async () => { - - const { unmount } = renderHookWithProvider(() => useTokenDetectionPolling(), {state}); - - const mockedTokenDetectionController = jest.mocked(Engine.context.TokenDetectionController); - - expect(mockedTokenDetectionController.startPolling).toHaveBeenCalledTimes(1); - expect( - mockedTokenDetectionController.startPolling - ).toHaveBeenCalledWith({chainIds: [selectedChainId], address: selectedAddress}); - - expect(mockedTokenDetectionController.stopPollingByPollingToken).toHaveBeenCalledTimes(0); - unmount(); - expect(mockedTokenDetectionController.stopPollingByPollingToken).toHaveBeenCalledTimes(1); - - }); - - it('Should not poll when token detection is disabled', async () => { - - renderHookWithProvider(() => useTokenDetectionPolling({chainIds: ['0x1']}), {state:{ - ...state, - engine: { - ...state.engine, - backgroundState: { - ...state.engine.backgroundState, - PreferencesController: { - ...state.engine.backgroundState.PreferencesController, - useTokenDetection: false, - }, - }, - }, - }}); - - const mockedTokenDetectionController = jest.mocked(Engine.context.TokenDetectionController); - expect(mockedTokenDetectionController.startPolling).toHaveBeenCalledTimes(0); - expect(mockedTokenDetectionController.stopPollingByPollingToken).toHaveBeenCalledTimes(0); - }); -}); diff --git a/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts b/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts deleted file mode 100644 index d07ba5c46f92..000000000000 --- a/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useSelector } from 'react-redux'; -import usePolling from '../usePolling'; -import Engine from '../../../core/Engine'; -import { selectChainId, selectNetworkConfigurations } from '../../../selectors/networkController'; -import { Hex } from '@metamask/utils'; -import { isPortfolioViewEnabled } from '../../../util/networks'; -import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; -import { selectUseTokenDetection } from '../../../selectors/preferencesController'; - -const useTokenDetectionPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { - - const networkConfigurations = useSelector(selectNetworkConfigurations); - const currentChainId = useSelector(selectChainId); - const selectedAccount = useSelector(selectSelectedInternalAccount); - const useTokenDetection = useSelector(selectUseTokenDetection); - - const chainIdsToPoll = isPortfolioViewEnabled - ? (chainIds ?? Object.keys(networkConfigurations)) - : [currentChainId]; - - const { TokenDetectionController } = Engine.context; - - usePolling({ - startPolling: - TokenDetectionController.startPolling.bind(TokenDetectionController), - stopPollingByPollingToken: - TokenDetectionController.stopPollingByPollingToken.bind(TokenDetectionController), - input: useTokenDetection ? [{ - chainIds: chainIdsToPoll as Hex[], - address: selectedAccount?.address as Hex - }] : [] - }); - - return { }; -}; - -export default useTokenDetectionPolling; diff --git a/app/components/hooks/AssetPolling/useTokenListPolling.test.ts b/app/components/hooks/AssetPolling/useTokenListPolling.test.ts deleted file mode 100644 index cbf1ff805e5d..000000000000 --- a/app/components/hooks/AssetPolling/useTokenListPolling.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; -import Engine from '../../../core/Engine'; -import useTokenListPolling from './useTokenListPolling'; - -jest.mock('../../../core/Engine', () => ({ - context: { - TokenListController: { - startPolling: jest.fn(), - stopPollingByPollingToken: jest.fn(), - }, - }, -})); - -describe('useTokenListPolling', () => { - - beforeEach(() => { - jest.resetAllMocks(); - }); - - const selectedChainId = '0x1' as const; - const state = { - engine: { - backgroundState: { - NetworkController: { - selectedNetworkClientId: 'selectedNetworkClientId', - networkConfigurationsByChainId: { - [selectedChainId]: { - chainId: selectedChainId, - rpcEndpoints: [{ - networkClientId: 'selectedNetworkClientId', - }] - }, - '0x89': {}, - }, - }, - }, - }, - }; - - it('Should poll by selected chain id, and stop polling on dismount', async () => { - - const { unmount } = renderHookWithProvider(() => useTokenListPolling(), {state}); - - const mockedTokenListController = jest.mocked(Engine.context.TokenListController); - - expect(mockedTokenListController.startPolling).toHaveBeenCalledTimes(1); - expect( - mockedTokenListController.startPolling - ).toHaveBeenCalledWith({chainId: selectedChainId}); - - expect(mockedTokenListController.stopPollingByPollingToken).toHaveBeenCalledTimes(0); - unmount(); - expect(mockedTokenListController.stopPollingByPollingToken).toHaveBeenCalledTimes(1); - }); -}); diff --git a/app/components/hooks/AssetPolling/useTokenListPolling.ts b/app/components/hooks/AssetPolling/useTokenListPolling.ts deleted file mode 100644 index 13bc408efd89..000000000000 --- a/app/components/hooks/AssetPolling/useTokenListPolling.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useSelector } from 'react-redux'; -import usePolling from '../usePolling'; -import Engine from '../../../core/Engine'; -import { selectChainId, selectNetworkConfigurations } from '../../../selectors/networkController'; -import { Hex } from '@metamask/utils'; -import { isPortfolioViewEnabled } from '../../../util/networks'; -import { selectERC20TokensByChain, selectTokenList } from '../../../selectors/tokenListController'; - -const useTokenListPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { - - // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); - const currentChainId = useSelector(selectChainId); - - // Selectors returning state updated by the polling - const tokenList = useSelector(selectTokenList); - const tokenListByChain = useSelector(selectERC20TokensByChain); - - const chainIdsToPoll = isPortfolioViewEnabled - ? (chainIds ?? Object.keys(networkConfigurations)) - : [currentChainId]; - - const { TokenListController } = Engine.context; - - usePolling({ - startPolling: - TokenListController.startPolling.bind(TokenListController), - stopPollingByPollingToken: - TokenListController.stopPollingByPollingToken.bind(TokenListController), - input: chainIdsToPoll.map((chainId) => ({ chainId: chainId as Hex })) - }); - - return { - tokenList, - tokenListByChain, - }; -}; - -export default useTokenListPolling; diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts deleted file mode 100644 index 55ae2b6292a2..000000000000 --- a/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import useTokenRatesPolling from './useTokenRatesPolling'; -import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; -import Engine from '../../../core/Engine'; - -jest.mock('../../../core/Engine', () => ({ - context: { - TokenRatesController: { - startPolling: jest.fn(), - stopPollingByPollingToken: jest.fn(), - }, - }, -})); - -describe('useTokenRatesPolling', () => { - - beforeEach(() => { - jest.resetAllMocks(); - }); - - const state = { - engine: { - backgroundState: { - TokenRatesController: { - marketData: {}, - }, - NetworkController: { - networkConfigurationsByChainId: { - '0x1': {}, - '0x89': {}, - }, - }, - }, - }, - }; - - it('Should poll by provided chain ids, and stop polling on dismount', async () => { - - const { unmount } = renderHookWithProvider(() => useTokenRatesPolling({chainIds: ['0x1']}), {state}); - - const mockedTokenRatesController = jest.mocked(Engine.context.TokenRatesController); - - expect(mockedTokenRatesController.startPolling).toHaveBeenCalledTimes(1); - expect( - mockedTokenRatesController.startPolling - ).toHaveBeenCalledWith({chainId: '0x1'}); - - expect(mockedTokenRatesController.stopPollingByPollingToken).toHaveBeenCalledTimes(0); - unmount(); - expect(mockedTokenRatesController.stopPollingByPollingToken).toHaveBeenCalledTimes(1); - }); -}); diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.ts deleted file mode 100644 index 093a5ef7a453..000000000000 --- a/app/components/hooks/AssetPolling/useTokenRatesPolling.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useSelector } from 'react-redux'; -import usePolling from '../usePolling'; -import Engine from '../../../core/Engine'; -import { selectChainId, selectNetworkConfigurations } from '../../../selectors/networkController'; -import { Hex } from '@metamask/utils'; -import { selectContractExchangeRates, selectTokenMarketData } from '../../../selectors/tokenRatesController'; -import { isPortfolioViewEnabled } from '../../../util/networks'; - -const useTokenRatesPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { - - // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); - const currentChainId = useSelector(selectChainId); - - // Selectors returning state updated by the polling - const contractExchangeRates = useSelector(selectContractExchangeRates); - const tokenMarketData = useSelector(selectTokenMarketData); - - const chainIdsToPoll = isPortfolioViewEnabled - ? (chainIds ?? Object.keys(networkConfigurations)) - : [currentChainId]; - - const { TokenRatesController } = Engine.context; - - usePolling({ - startPolling: - TokenRatesController.startPolling.bind(TokenRatesController), - stopPollingByPollingToken: - TokenRatesController.stopPollingByPollingToken.bind(TokenRatesController), - input: chainIdsToPoll.map((chainId) => ({chainId: chainId as Hex})), - }); - - return { - contractExchangeRates, - tokenMarketData - }; -}; - -export default useTokenRatesPolling; diff --git a/app/components/hooks/DisplayName/useDisplayName.test.ts b/app/components/hooks/DisplayName/useDisplayName.test.ts index 7b54514f6b9e..7380b5db35ad 100644 --- a/app/components/hooks/DisplayName/useDisplayName.test.ts +++ b/app/components/hooks/DisplayName/useDisplayName.test.ts @@ -3,7 +3,6 @@ import useDisplayName, { DisplayNameVariant } from './useDisplayName'; import { useFirstPartyContractNames } from './useFirstPartyContractNames'; import { useERC20Tokens } from './useERC20Tokens'; import { useWatchedNFTNames } from './useWatchedNFTNames'; -import { useNftNames } from './useNftName'; import { CHAIN_IDS } from '@metamask/transaction-controller'; const UNKNOWN_ADDRESS_CHECKSUMMED = @@ -25,9 +24,6 @@ jest.mock('./useFirstPartyContractNames', () => ({ jest.mock('./useERC20Tokens', () => ({ useERC20Tokens: jest.fn(), })); -jest.mock('./useNftName', () => ({ - useNftNames: jest.fn(), -})); describe('useDisplayName', () => { const mockUseWatchedNFTNames = jest.mocked(useWatchedNFTNames); @@ -35,14 +31,12 @@ describe('useDisplayName', () => { useFirstPartyContractNames, ); const mockUseERC20Tokens = jest.mocked(useERC20Tokens); - const mockUseNFTNames = jest.mocked(useNftNames); beforeEach(() => { jest.resetAllMocks(); mockUseWatchedNFTNames.mockReturnValue([]); mockUseFirstPartyContractNames.mockReturnValue([]); mockUseERC20Tokens.mockReturnValue([]); - mockUseNFTNames.mockReturnValue([]); }); describe('unknown address', () => { diff --git a/app/components/hooks/DisplayName/useDisplayName.ts b/app/components/hooks/DisplayName/useDisplayName.ts index edb5f1839a2a..4d77af3fcfeb 100644 --- a/app/components/hooks/DisplayName/useDisplayName.ts +++ b/app/components/hooks/DisplayName/useDisplayName.ts @@ -2,7 +2,6 @@ import { NameType } from '../../UI/Name/Name.types'; import { useFirstPartyContractNames } from './useFirstPartyContractNames'; import { useWatchedNFTNames } from './useWatchedNFTNames'; import { useERC20Tokens } from './useERC20Tokens'; -import { useNftNames } from './useNftName'; export interface UseDisplayNameRequest { preferContractSymbol?: boolean; @@ -73,30 +72,22 @@ export function useDisplayNames( const firstPartyContractNames = useFirstPartyContractNames(requests); const watchedNftNames = useWatchedNFTNames(requests); const erc20Tokens = useERC20Tokens(requests); - const nftNames = useNftNames(requests); return requests.map((_request, index) => { const watchedNftName = watchedNftNames[index]; const firstPartyContractName = firstPartyContractNames[index]; const erc20Token = erc20Tokens[index]; - const { name: nftCollectionName, image: nftCollectionImage } = - nftNames[index] || {}; const name = - watchedNftName || - firstPartyContractName || - erc20Token?.name || - nftCollectionName; + watchedNftName || firstPartyContractName || erc20Token?.name; - const image = erc20Token?.image || nftCollectionImage; + const image = erc20Token?.image; return { contractDisplayName: erc20Token?.name, image, name, - variant: name - ? DisplayNameVariant.Recognized - : DisplayNameVariant.Unknown, + variant: name ? DisplayNameVariant.Recognized : DisplayNameVariant.Unknown, }; }); } diff --git a/app/components/hooks/DisplayName/useNftCollectionsMetadata.test.ts b/app/components/hooks/DisplayName/useNftCollectionsMetadata.test.ts deleted file mode 100644 index eaa5a4d43611..000000000000 --- a/app/components/hooks/DisplayName/useNftCollectionsMetadata.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { SimulationTokenStandard } from '@metamask/transaction-controller'; -import { - useNftCollectionsMetadata, - TokenStandard, -} from './useNftCollectionsMetadata'; -import Engine from '../../../core/Engine'; -import { getTokenDetails } from '../../../util/address'; - -jest.mock('../../../util/address', () => ({ - getTokenDetails: jest.fn(), -})); - -jest.mock('../../../selectors/networkController', () => ({ - selectChainId: jest.fn(), -})); - -jest.mock('../../../core/Engine', () => ({ - context: { - NftController: { - getNFTContractInfo: jest.fn(), - }, - }, -})); - -const CHAIN_ID_MOCK = '0x1'; -const ERC_721_ADDRESS_1 = '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb'; -const ERC_721_COLLECTION_1_MOCK = { - image: 'url1', - isSpam: false, - name: 'Erc 721 1', -}; - -const ERC_721_ADDRESS_2 = '0x06012c8cf97bead5deae237070f9587f8e7a266d'; -const ERC_721_COLLECTION_2_MOCK = { - image: 'url2', - isSpam: false, - name: 'Erc 721 2', -}; - -describe('useNftCollectionsMetadata', () => { - const { NftController } = Engine.context; - const mockGetTokenDetails = jest.mocked(getTokenDetails); - const mockGetNFTContractInfo = jest.mocked(NftController.getNFTContractInfo); - - beforeEach(() => { - jest.resetAllMocks(); - mockGetNFTContractInfo.mockResolvedValue({ - collections: [ERC_721_COLLECTION_1_MOCK, ERC_721_COLLECTION_2_MOCK], - }); - mockGetTokenDetails - .mockResolvedValueOnce({ - name: 'TEST', - symbol: 'TST', - standard: TokenStandard.erc721, - }) - .mockResolvedValueOnce({ - name: 'TEST', - symbol: 'TST', - standard: TokenStandard.erc721, - }); - }); - - it('calls NFT tokens API and returns the correct data structure', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useNftCollectionsMetadata([ - { - contractAddress: ERC_721_ADDRESS_1, - chainId: CHAIN_ID_MOCK, - }, - { - contractAddress: ERC_721_ADDRESS_2, - chainId: CHAIN_ID_MOCK, - }, - ]), - ); - - await waitForNextUpdate(); - - expect(mockGetNFTContractInfo).toHaveBeenCalledTimes(1); - - expect(result.current).toStrictEqual({ - [CHAIN_ID_MOCK]: { - [ERC_721_ADDRESS_1.toLowerCase()]: ERC_721_COLLECTION_1_MOCK, - [ERC_721_ADDRESS_2.toLowerCase()]: ERC_721_COLLECTION_2_MOCK, - }, - }); - }); - - describe('does not call NFT tokens API', () => { - it('if there are no contracts to fetch', async () => { - renderHook(() => useNftCollectionsMetadata([])); - expect(mockGetNFTContractInfo).not.toHaveBeenCalled(); - }); - - it('if there are no valid nft request', async () => { - // getTokenStandardAndDetails returns that the standard is ERC20 - mockGetTokenDetails.mockReset().mockResolvedValueOnce({ - name: 'TEST', - symbol: 'TST', - standard: SimulationTokenStandard.erc20, - }); - - renderHook(() => - useNftCollectionsMetadata([ - { - contractAddress: '0xERC20Address', - chainId: CHAIN_ID_MOCK, - }, - ]), - ); - expect(mockGetNFTContractInfo).not.toHaveBeenCalled(); - }); - - it('if token standard request fails', async () => { - mockGetTokenDetails.mockReset().mockRejectedValue(new Error('api error')); - - renderHook(() => - useNftCollectionsMetadata([ - { - contractAddress: '0xERC20Address', - chainId: CHAIN_ID_MOCK, - }, - ]), - ); - expect(mockGetNFTContractInfo).not.toHaveBeenCalled(); - }); - }); - - it('does memoise result for same requests', async () => { - const { waitForNextUpdate, rerender } = renderHook(() => - useNftCollectionsMetadata([ - { - contractAddress: ERC_721_ADDRESS_1, - chainId: CHAIN_ID_MOCK, - }, - { - contractAddress: ERC_721_ADDRESS_2, - chainId: CHAIN_ID_MOCK, - }, - ]), - ); - - await waitForNextUpdate(); - rerender(); - - expect(mockGetNFTContractInfo).toHaveBeenCalledTimes(1); - }); -}); diff --git a/app/components/hooks/DisplayName/useNftCollectionsMetadata.ts b/app/components/hooks/DisplayName/useNftCollectionsMetadata.ts deleted file mode 100644 index e3b7c5cbfaa4..000000000000 --- a/app/components/hooks/DisplayName/useNftCollectionsMetadata.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Collection } from '@metamask/assets-controllers'; -import type { Hex } from '@metamask/utils'; -import { useAsyncResult } from '../useAsyncResult'; -import Engine from '../../../core/Engine'; -import { getTokenDetails } from '../../../util/address'; - -export enum TokenStandard { - erc20 = 'ERC20', - erc721 = 'ERC721', - erc1155 = 'ERC1155', -} - -export interface UseNftCollectionsMetadataRequest { - chainId: string; - contractAddress: string; -} - -// For now, we only support ERC721 tokens -const SUPPORTED_NFT_TOKEN_STANDARDS = [TokenStandard.erc721]; - -export function useNftCollectionsMetadata( - requests: UseNftCollectionsMetadataRequest[], -): Record<string, Record<string, Collection>> { - const { value: collectionsMetadata } = useAsyncResult( - () => fetchCollections(requests), - [JSON.stringify(requests)], - ); - - return collectionsMetadata ?? {}; -} - -async function fetchCollections(requests: UseNftCollectionsMetadataRequest[]) { - const valuesByChainId = requests.reduce<Record<string, string[]>>( - (acc, { chainId, contractAddress }) => { - acc[chainId] = [...(acc[chainId] ?? []), contractAddress.toLowerCase()]; - return acc; - }, - {}, - ); - - const chainIds = Object.keys(valuesByChainId); - - const responses = await Promise.all( - chainIds.map((chainId) => { - const contractAddresses = valuesByChainId[chainId]; - return fetchCollectionsForChain(contractAddresses, chainId); - }), - ); - - return chainIds.reduce<Record<string, Record<string, Collection>>>( - (acc, chainId, index) => { - acc[chainId] = responses[index]; - return acc; - }, - {}, - ); -} - -async function fetchCollectionsForChain( - contractAddresses: string[], - chainId: string, -) { - const { NftController } = Engine.context; - - const contractStandardsResponses = await Promise.all( - contractAddresses.map((contractAddress) => - getTokenDetails(contractAddress, chainId), - ), - ); - - const supportedNFTContracts = contractAddresses.filter( - (_contractAddress, index) => - SUPPORTED_NFT_TOKEN_STANDARDS.includes( - contractStandardsResponses[index].standard as TokenStandard, - ), - ); - - if (supportedNFTContracts.length === 0) { - return {}; - } - - const collectionsResult = await NftController.getNFTContractInfo( - supportedNFTContracts, - chainId as Hex, - ); - - const collectionsData = collectionsResult.collections.reduce< - Record<string, Collection> - >((acc, collection, index) => { - acc[supportedNFTContracts[index]] = { - name: collection?.name, - image: collection?.image, - isSpam: collection?.isSpam, - }; - return acc; - }, {}); - - return collectionsData; -} - - - - - - - - diff --git a/app/components/hooks/DisplayName/useNftName.test.ts b/app/components/hooks/DisplayName/useNftName.test.ts deleted file mode 100644 index 567045e2f13e..000000000000 --- a/app/components/hooks/DisplayName/useNftName.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useNftNames } from './useNftName'; -import { useNftCollectionsMetadata } from './useNftCollectionsMetadata'; -import { NameType } from '../../UI/Name/Name.types'; - -const CHAIN_ID_MOCK = '0x1'; -const KNOWN_NFT_VALUE = '0x495f947276749Ce646f68AC8c248420045cb7b5e'; -const KNOWN_NFT_NAME = 'Known NFT'; -const KNOWN_NFT_IMAGE = 'https://example.com/nft-image.png'; -const NFT_COLLECTIONS_MOCK = { - [KNOWN_NFT_VALUE.toLowerCase()]: { - name: KNOWN_NFT_NAME, - image: KNOWN_NFT_IMAGE, - isSpam: false, - }, -}; - -jest.mock('./useNftCollectionsMetadata', () => ({ - useNftCollectionsMetadata: jest.fn(), -})); - -describe('useNftNames', () => { - const useNftCollectionsMetadataMock = jest.mocked(useNftCollectionsMetadata); - - beforeAll(() => { - useNftCollectionsMetadataMock.mockReturnValue({ - [CHAIN_ID_MOCK]: NFT_COLLECTIONS_MOCK, - }); - }); - - it('returns the correct NFT name and image when not spam', () => { - const responses = useNftNames([ - { - value: KNOWN_NFT_VALUE, - type: NameType.EthereumAddress, - variation: CHAIN_ID_MOCK, - }, - ]); - expect(responses[0]?.name).toEqual(KNOWN_NFT_NAME); - expect(responses[0]?.image).toEqual(KNOWN_NFT_IMAGE); - }); - - it('returns undefined for name and image if NFT is spam', () => { - useNftCollectionsMetadataMock.mockReturnValue({ - [CHAIN_ID_MOCK]: { - [KNOWN_NFT_VALUE.toLowerCase()]: { - name: KNOWN_NFT_NAME, - image: KNOWN_NFT_IMAGE, - isSpam: true, - }, - }, - }); - const responses = useNftNames([ - { - value: KNOWN_NFT_VALUE, - type: NameType.EthereumAddress, - variation: CHAIN_ID_MOCK, - }, - ]); - expect(responses[0]?.name).toBeUndefined(); - expect(responses[0]?.image).toBeUndefined(); - }); - - it('returns undefined for name and image if no NFT matched', () => { - useNftCollectionsMetadataMock.mockReturnValue({}); - const responses = useNftNames([ - { - value: KNOWN_NFT_VALUE, - type: NameType.EthereumAddress, - variation: CHAIN_ID_MOCK, - }, - ]); - expect(responses[0]?.name).toBeUndefined(); - expect(responses[0]?.image).toBeUndefined(); - }); -}); diff --git a/app/components/hooks/DisplayName/useNftName.ts b/app/components/hooks/DisplayName/useNftName.ts deleted file mode 100644 index ed05e00cc054..000000000000 --- a/app/components/hooks/DisplayName/useNftName.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { NameType } from '../../UI/Name/Name.types'; -import { UseDisplayNameRequest } from './useDisplayName'; -import { useNftCollectionsMetadata } from './useNftCollectionsMetadata'; - -export interface UseNFTNameResponse { - name: string | undefined; - image: string | undefined; -} - -/** - * Get the display name and image for the given value. - * - * @param value The value to get the display name for. - */ -export function useNftNames( - nameRequests: UseDisplayNameRequest[], -): (UseNFTNameResponse | undefined)[] { - const requests = nameRequests - .filter(({ type }) => type === NameType.EthereumAddress) - .map(({ value, variation }) => ({ - chainId: variation, - contractAddress: value, - })); - - const nftCollectionsByAddressByChain = useNftCollectionsMetadata(requests); - - return nameRequests.map( - ({ type, value: contractAddress, variation: chainId }) => { - if (type !== NameType.EthereumAddress) { - return undefined; - } - - const nftCollectionProperties = - nftCollectionsByAddressByChain[chainId]?.[ - contractAddress.toLowerCase() - ]; - - const isSpam = nftCollectionProperties?.isSpam !== false; - - if (!nftCollectionProperties || isSpam) { - return undefined; - } - - const { name, image } = nftCollectionProperties; - - return { name, image }; - }, - ); -} diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index e3eef28d2b7b..3b3dfcc7ced5 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -43,6 +43,7 @@ const Routes = { MODAL_CONFIRMATION: 'ModalConfirmation', MODAL_MANDATORY: 'ModalMandatory', WHATS_NEW: 'WhatsNewModal', + SMART_TRANSACTIONS_OPT_IN: 'SmartTransactionsOptInModal', TURN_OFF_REMEMBER_ME: 'TurnOffRememberMeModal', UPDATE_NEEDED: 'UpdateNeededModal', ENABLE_AUTOMATIC_SECURITY_CHECKS: 'EnableAutomaticSecurityChecksModal', diff --git a/app/core/Engine.ts b/app/core/Engine.ts index decf28da51f1..55d6fdbf2e35 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -115,10 +115,7 @@ import { SubjectMetadataControllerState, ///: END:ONLY_INCLUDE_IF } from '@metamask/permission-controller'; -import SwapsController, { - swapsUtils, - SwapsControllerState, -} from '@metamask/swaps-controller'; +import SwapsController, { swapsUtils } from '@metamask/swaps-controller'; import { PPOMController, PPOMControllerActions, @@ -218,6 +215,8 @@ import { SignatureControllerOptions, } from '@metamask/signature-controller'; import { hasProperty, Hex, Json } from '@metamask/utils'; +// TODO: Export this type from the package directly +import { SwapsState } from '@metamask/swaps-controller/dist/SwapsController'; import { providerErrors } from '@metamask/rpc-errors'; import { PPOM, ppomInit } from '../lib/ppom/PPOMView'; @@ -374,7 +373,7 @@ export interface EngineState { TokenRatesController: TokenRatesControllerState; TransactionController: TransactionControllerState; SmartTransactionsController: SmartTransactionsControllerState; - SwapsController: SwapsControllerState; + SwapsController: SwapsState; GasFeeController: GasFeeState; TokensController: TokensControllerState; TokenDetectionController: BaseState; @@ -552,7 +551,6 @@ export class Engine { useNftDetection: true, // set this to true to enable nft detection by default to new users displayNftMedia: true, securityAlertsEnabled: true, - smartTransactionsOptInStatus: true, tokenSortConfig: { key: 'tokenFiatAmount', order: 'dsc', @@ -1029,6 +1027,7 @@ export class Engine { }); const selectedNetworkController = new SelectedNetworkController({ + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'SelectedNetworkController', allowedActions: [ @@ -1543,9 +1542,6 @@ export class Engine { assetsContractController.getBalancesInSingleCall.bind( assetsContractController, ), - platform: 'mobile', - useAccountsAPI: true, - disabled: false }), new NftDetectionController({ @@ -1609,43 +1605,33 @@ export class Engine { }), this.transactionController, this.smartTransactionsController, - new SwapsController({ - clientId: AppConstants.SWAPS.CLIENT_ID, - fetchAggregatorMetadataThreshold: - AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD, - fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD, - fetchTopAssetsThreshold: AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD, - supportedChainIds: [ - swapsUtils.ETH_CHAIN_ID, - swapsUtils.BSC_CHAIN_ID, - swapsUtils.SWAPS_TESTNET_CHAIN_ID, - swapsUtils.POLYGON_CHAIN_ID, - swapsUtils.AVALANCHE_CHAIN_ID, - swapsUtils.ARBITRUM_CHAIN_ID, - swapsUtils.OPTIMISM_CHAIN_ID, - swapsUtils.ZKSYNC_ERA_CHAIN_ID, - swapsUtils.LINEA_CHAIN_ID, - swapsUtils.BASE_CHAIN_ID, - ], - // @ts-expect-error TODO: Resolve new typing for restricted controller messenger - messenger: this.controllerMessenger.getRestricted({ - name: 'SwapsController', - // TODO: allow these internal calls once GasFeeController - // export these action types and register its action handlers - // allowedActions: [ - // 'GasFeeController:getEIP1559GasFeeEstimates', - // ], - allowedActions: [ - 'NetworkController:findNetworkClientIdByChainId', - 'NetworkController:getNetworkClientById', + new SwapsController( + { + fetchGasFeeEstimates: () => gasFeeController.fetchGasFeeEstimates(), + // @ts-expect-error TODO: Resolve mismatch between gas fee and swaps controller types + fetchEstimatedMultiLayerL1Fee, + }, + { + clientId: AppConstants.SWAPS.CLIENT_ID, + fetchAggregatorMetadataThreshold: + AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD, + fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD, + fetchTopAssetsThreshold: + AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD, + supportedChainIds: [ + swapsUtils.ETH_CHAIN_ID, + swapsUtils.BSC_CHAIN_ID, + swapsUtils.SWAPS_TESTNET_CHAIN_ID, + swapsUtils.POLYGON_CHAIN_ID, + swapsUtils.AVALANCHE_CHAIN_ID, + swapsUtils.ARBITRUM_CHAIN_ID, + swapsUtils.OPTIMISM_CHAIN_ID, + swapsUtils.ZKSYNC_ERA_CHAIN_ID, + swapsUtils.LINEA_CHAIN_ID, + swapsUtils.BASE_CHAIN_ID, ], - allowedEvents: [], - }), - // TODO: Remove once GasFeeController exports this action type - fetchGasFeeEstimates: () => gasFeeController.fetchGasFeeEstimates(), - // @ts-expect-error TODO: Resolve mismatch between gas fee and swaps controller types - fetchEstimatedMultiLayerL1Fee, - }), + }, + ), gasFeeController, approvalController, permissionController, @@ -1919,11 +1905,17 @@ export class Engine { startPolling() { const { + TokenDetectionController, + TokenListController, TransactionController, + TokenRatesController, } = this.context; + TokenListController.start(); + TokenDetectionController.start(); // leaving the reference of TransactionController here, rather than importing it from utils to avoid circular dependency TransactionController.startIncomingTransactionPolling(); + TokenRatesController.start(); } configureControllersOnNetworkChange() { @@ -1937,8 +1929,8 @@ export class Engine { } provider.sendAsync = provider.sendAsync.bind(provider); - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. - SwapsController.setProvider(provider, { + SwapsController.configure({ + provider, chainId: NetworkController.getNetworkClientById( NetworkController?.state.selectedNetworkClientId, ).configuration.chainId, @@ -2166,11 +2158,11 @@ export class Engine { // SelectedNetworkController.unsetAllDomains() //Clear assets info - TokensController.resetState(); - NftController.resetState(); + TokensController.reset(); + NftController.reset(); - TokenBalancesController.resetState(); - TokenRatesController.resetState(); + TokenBalancesController.reset(); + TokenRatesController.reset(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (TransactionController as any).update(() => ({ diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index ecbec9b87159..dfe8e51c0675 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -113,10 +113,7 @@ class EngineService { name: 'SmartTransactionsController', key: `${engine.context.SmartTransactionsController.name}:stateChange`, }, - { - name: 'SwapsController', - key: `${engine.context.SwapsController.name}:stateChange`, - }, + { name: 'SwapsController' }, { name: 'TokenListController', key: `${engine.context.TokenListController.name}:stateChange`, @@ -175,6 +172,18 @@ class EngineService { name: 'PPOMController', key: `${engine.context.PPOMController.name}:stateChange`, }, + { + name: 'AuthenticationController', + key: `AuthenticationController:stateChange`, + }, + { + name: 'UserStorageController', + key: `UserStorageController:stateChange`, + }, + { + name: 'NotificationServicesController', + key: `NotificationServicesController:stateChange`, + }, ]; engine?.datamodel?.subscribe?.(() => { diff --git a/app/core/NotificationManager.js b/app/core/NotificationManager.js index a021e381677c..22af910e5195 100644 --- a/app/core/NotificationManager.js +++ b/app/core/NotificationManager.js @@ -238,7 +238,7 @@ class NotificationManager { pollPromises.push( ...[ TokenBalancesController.poll(), - TokenDetectionController.detectTokens({ chainIds: [transactionMeta.chainId] }), + TokenDetectionController.start(), ], ); break; diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 4f3580e0d922..033b42c91587 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -51,12 +51,6 @@ const Engine = ImportedEngine as any; let appVersion = ''; -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -export const SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES = { - showNameSnapAccount: 'snap_manageAccounts:showNameSnapAccount', -}; -///: END:ONLY_INCLUDE_IF - export enum ApprovalTypes { CONNECT_ACCOUNTS = 'CONNECT_ACCOUNTS', SIGN_MESSAGE = 'SIGN_MESSAGE', diff --git a/app/core/SnapKeyring/SnapKeyring.test.ts b/app/core/SnapKeyring/SnapKeyring.test.ts index 0e8a181830b5..fad726e02949 100644 --- a/app/core/SnapKeyring/SnapKeyring.test.ts +++ b/app/core/SnapKeyring/SnapKeyring.test.ts @@ -10,11 +10,7 @@ import { SnapKeyringBuilderMessenger, } from './types'; import { SnapId } from '@metamask/snaps-sdk'; -import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../RPCMethods/RPCMethodMiddleware'; -const mockAddRequest = jest.fn(); -const mockStartFlow = jest.fn(); -const mockEndFlow = jest.fn(); const mockGetAccounts = jest.fn(); const mockSnapId: SnapId = 'snapId' as SnapId; const mockSnapName = 'mock-snap'; @@ -23,9 +19,7 @@ const mockPersisKeyringHelper = jest.fn(); const mockSetSelectedAccount = jest.fn(); const mockRemoveAccountHelper = jest.fn(); const mockGetAccountByAddress = jest.fn(); -const mockSetAccountName = jest.fn(); -const mockFlowId = '123'; const address = '0x2a4d4b667D5f12C3F9Bf8F14a7B9f8D8d9b8c8fA'; const accountNameSuggestion = 'Suggested Account Name'; const mockAccount = { @@ -83,20 +77,15 @@ const createControllerMessenger = ({ const [actionType, ...params]: any[] = args; switch (actionType) { - case 'ApprovalController:startFlow': - return mockStartFlow.mockReturnValue({ id: mockFlowId })(); - case 'ApprovalController:addRequest': - return mockAddRequest(params); - case 'ApprovalController:endFlow': - return mockEndFlow.mockReturnValue(true)(params); case 'KeyringController:getAccounts': return mockGetAccounts.mockResolvedValue([])(); + case 'AccountsController:getAccountByAddress': return mockGetAccountByAddress.mockReturnValue(account)(params); + case 'AccountsController:setSelectedAccount': return mockSetSelectedAccount(params); - case 'AccountsController:setAccountName': - return mockSetAccountName.mockReturnValue(null)(params); + default: throw new Error( `MOCK_FAIL - unsupported messenger call: ${actionType}`, @@ -121,9 +110,6 @@ describe('Snap Keyring Methods', () => { }); describe('addAccount', () => { - beforeEach(() => { - mockAddRequest.mockReturnValue(true).mockReturnValue({ success: true }); - }); afterEach(() => { jest.resetAllMocks(); }); @@ -134,69 +120,14 @@ describe('Snap Keyring Methods', () => { method: KeyringEvent.AccountCreated, params: { account: mockAccount, - displayConfirmation: false, - }, - }); - - expect(mockStartFlow).toHaveBeenCalledTimes(1); - expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ - { - origin: mockSnapId, - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: '', - }, - }, - true, - ]); - expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); - expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); - expect(mockGetAccountByAddress).toHaveBeenCalledWith([ - mockAccount.address.toLowerCase(), - ]); - expect(mockSetAccountName).not.toHaveBeenCalled(); - expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); - }); - - it('handles account creation with user defined name', async () => { - const mockNameSuggestion = 'suggested name'; - mockAddRequest.mockReturnValueOnce({ - success: true, - name: mockNameSuggestion, - }); - const builder = createSnapKeyringBuilder(); - await builder().handleKeyringSnapMessage(mockSnapId, { - method: KeyringEvent.AccountCreated, - params: { - account: mockAccount, - displayConfirmation: false, - accountNameSuggestion: mockNameSuggestion, + displayConfirmation: true, }, }); - - expect(mockStartFlow).toHaveBeenCalledTimes(1); expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); - expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ - { - origin: mockSnapId, - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: mockNameSuggestion, - }, - }, - true, - ]); expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); expect(mockGetAccountByAddress).toHaveBeenCalledWith([ mockAccount.address.toLowerCase(), ]); - expect(mockSetAccountName).toHaveBeenCalledTimes(1); - expect(mockSetAccountName).toHaveBeenCalledWith([ - mockAccount.id, - mockNameSuggestion, - ]); - expect(mockEndFlow).toHaveBeenCalledTimes(1); - expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); }); }); }); diff --git a/app/core/SnapKeyring/SnapKeyring.ts b/app/core/SnapKeyring/SnapKeyring.ts index 6d63ce6709d0..ec5e9e5659fe 100644 --- a/app/core/SnapKeyring/SnapKeyring.ts +++ b/app/core/SnapKeyring/SnapKeyring.ts @@ -2,7 +2,6 @@ import { SnapKeyring } from '@metamask/eth-snap-keyring'; import type { SnapController } from '@metamask/snaps-controllers'; import { SnapKeyringBuilderMessenger } from './types'; import Logger from '../../util/Logger'; -import { showAccountNameSuggestionDialog } from './utils/showDialog'; /** * Constructs a SnapKeyring builder with specified handlers for managing snap accounts. @@ -50,64 +49,34 @@ export const snapKeyringBuilder = ( snapId: string, handleUserInput: (accepted: boolean) => Promise<void>, accountNameSuggestion = '', + displayConfirmation = false, ) => { - const { id: addAccountFlowId } = controllerMessenger.call( - 'ApprovalController:startFlow', + // TODO: Implement proper snap account confirmations. Currently, we are approving everything for testing purposes. + Logger.log( + `SnapKeyring: addAccount called with \n + - address: ${address} \n + - handleUserInput: ${handleUserInput} \n + - snapId: ${snapId} \n + - accountNameSuggestion: ${accountNameSuggestion} \n + - displayConfirmation: ${displayConfirmation}`, ); - try { - const accountNameConfirmationResult = - await showAccountNameSuggestionDialog( - snapId, - controllerMessenger, - accountNameSuggestion, - ); - - if (accountNameConfirmationResult.success) { - try { - await persistKeyringHelper(); - await handleUserInput(accountNameConfirmationResult.success); - const account = controllerMessenger.call( - 'AccountsController:getAccountByAddress', - address, - ); - if (!account) { - throw new Error( - `Internal account not found for address: ${address}`, - ); - } - - // Set the selected account to the new account - controllerMessenger.call( - 'AccountsController:setSelectedAccount', - account.id, - ); - - if (accountNameConfirmationResult.name) { - controllerMessenger.call( - 'AccountsController:setAccountName', - account.id, - accountNameConfirmationResult.name, - ); - } - } catch (e) { - // Error occurred while naming the account - const error = (e as Error).message; - throw new Error( - `Error occurred while creating snap account: ${error}`, - ); - } - } else { - // User has cancelled account creation so remove the account from the keyring - await handleUserInput(accountNameConfirmationResult?.success); - - throw new Error('User denied account creation'); - } - } finally { - controllerMessenger.call('ApprovalController:endFlow', { - id: addAccountFlowId, - }); + // Approve everything for now because we have not implemented snap account confirmations yet + await handleUserInput(true); + await persistKeyringHelper(); + const account = controllerMessenger.call( + 'AccountsController:getAccountByAddress', + address, + ); + if (!account) { + throw new Error(`Internal account not found for address: ${address}`); } + + // Set the selected account to the new account + controllerMessenger.call( + 'AccountsController:setSelectedAccount', + account.id, + ); }, removeAccount: async ( diff --git a/app/core/SnapKeyring/utils/showDialog.ts b/app/core/SnapKeyring/utils/showDialog.ts deleted file mode 100644 index 9358adc79d50..000000000000 --- a/app/core/SnapKeyring/utils/showDialog.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../RPCMethods/RPCMethodMiddleware'; -import { SnapKeyringBuilderMessenger } from '../types'; - -interface CreateAccountConfirmationResult { - success: boolean; - name?: string; -} - -/** - * Show the account name suggestion confirmation dialog for a given Snap. - * - * @param snapId - Snap ID to show the account name suggestion dialog for. - * @param controllerMessenger - The controller messenger instance. - * @param accountNameSuggestion - Suggested name for the new account. - * @returns The user's confirmation result. - */ -export async function showAccountNameSuggestionDialog( - snapId: string, - controllerMessenger: SnapKeyringBuilderMessenger, - accountNameSuggestion: string, -): Promise<CreateAccountConfirmationResult> { - try { - const confirmationResult = (await controllerMessenger.call( - 'ApprovalController:addRequest', - { - origin: snapId, - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, - requestData: { - snapSuggestedAccountName: accountNameSuggestion, - }, - }, - true, - )) as CreateAccountConfirmationResult; - - if (confirmationResult) { - return { - success: confirmationResult.success, - name: confirmationResult.name, - }; - } - return { success: false }; - } catch (e) { - throw new Error(`Error occurred while showing name account dialog.\n${e}`); - } -} diff --git a/app/core/redux/slices/smartTransactions/index.test.ts b/app/core/redux/slices/smartTransactions/index.test.ts new file mode 100644 index 000000000000..f97ff4436a6d --- /dev/null +++ b/app/core/redux/slices/smartTransactions/index.test.ts @@ -0,0 +1,25 @@ +import reducer, { + updateOptInModalAppVersionSeen, + SmartTransactionsState, +} from '.'; + +describe('smartTransactions slice', () => { + // Define the initial state for your tests + const initialState: SmartTransactionsState = { + optInModalAppVersionSeen: null, + }; + + it('should handle initial state', () => { + expect(reducer(undefined, { type: 'unknown' })).toEqual({ + optInModalAppVersionSeen: null, + }); + }); + + it('should handle updateOptInModalAppVersionSeen', () => { + const actual = reducer( + initialState, + updateOptInModalAppVersionSeen('2.0.0'), + ); + expect(actual.optInModalAppVersionSeen).toEqual('2.0.0'); + }); +}); diff --git a/app/core/redux/slices/smartTransactions/index.ts b/app/core/redux/slices/smartTransactions/index.ts new file mode 100644 index 000000000000..f644375d10a7 --- /dev/null +++ b/app/core/redux/slices/smartTransactions/index.ts @@ -0,0 +1,34 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; + +export interface SmartTransactionsState { + optInModalAppVersionSeen: string | null; +} + +export const initialState: SmartTransactionsState = { + optInModalAppVersionSeen: null, +}; + +const name = 'smartTransactions'; + +const slice = createSlice({ + name, + initialState, + reducers: { + /** + * Updates the the app version seen for the opt in modal. + * @param state - The current state of the smartTransactions slice. + * @param action - An action with the new app version seen as payload. + */ + updateOptInModalAppVersionSeen: (state, action: PayloadAction<string>) => { + state.optInModalAppVersionSeen = action.payload; + }, + }, +}); + +const { actions, reducer } = slice; + +export default reducer; + +// Actions / action-creators + +export const { updateOptInModalAppVersionSeen } = actions; diff --git a/app/reducers/index.ts b/app/reducers/index.ts index 39f5f91c8c89..cc8f3b7cd264 100644 --- a/app/reducers/index.ts +++ b/app/reducers/index.ts @@ -29,6 +29,7 @@ import rpcEventReducer from './rpcEvents'; import accountsReducer from './accounts'; import sdkReducer from './sdk'; import inpageProviderReducer from '../core/redux/slices/inpageProvider'; +import smartTransactionsReducer from '../core/redux/slices/smartTransactions'; import transactionMetricsReducer from '../core/redux/slices/transactionMetrics'; import originThrottlingReducer from '../core/redux/slices/originThrottling'; import notificationsAccountsProvider from '../core/redux/slices/notifications'; @@ -79,6 +80,7 @@ export interface RootState { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any transaction: any; + smartTransactions: StateFromReducer<typeof smartTransactionsReducer>; user: IUserReducer; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -143,6 +145,7 @@ const rootReducer = combineReducers<RootState, any>({ settings: settingsReducer, alert: alertReducer, transaction: transactionReducer, + smartTransactions: smartTransactionsReducer, user: userReducer, wizard: wizardReducer, onboarding: onboardingReducer, diff --git a/app/reducers/swaps/utils.ts b/app/reducers/swaps/utils.ts index 880e33d60cc0..3a3d7d1e002f 100644 --- a/app/reducers/swaps/utils.ts +++ b/app/reducers/swaps/utils.ts @@ -1,4 +1,4 @@ -import { FeatureFlags } from '@metamask/swaps-controller/dist/types'; +import { FeatureFlags } from '@metamask/swaps-controller/dist/swapsInterfaces'; import Device from '../../util/device'; import { CHAIN_ID_TO_NAME_MAP } from '@metamask/swaps-controller/dist/constants'; diff --git a/app/selectors/tokenRatesController.ts b/app/selectors/tokenRatesController.ts index 995e5988fd4d..e41d78a0dd39 100644 --- a/app/selectors/tokenRatesController.ts +++ b/app/selectors/tokenRatesController.ts @@ -14,9 +14,3 @@ export const selectContractExchangeRates = createSelector( (chainId: Hex, tokenRatesControllerState: TokenRatesControllerState) => tokenRatesControllerState.marketData[chainId], ); - -export const selectTokenMarketData = createSelector( - selectTokenRatesControllerState, - (tokenRatesControllerState: TokenRatesControllerState) => - tokenRatesControllerState.marketData, -); diff --git a/app/util/networks/index.js b/app/util/networks/index.js index b05627467d41..624915020315 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -592,6 +592,3 @@ export const isChainPermissionsFeatureEnabled = export const isPermissionsSettingsV1Enabled = process.env.MM_PERMISSIONS_SETTINGS_V1_ENABLED === '1'; - -export const isPortfolioViewEnabled = - process.env.PORTFOLIO_VIEW === 'true'; diff --git a/app/util/onboarding/index.test.ts b/app/util/onboarding/index.test.ts new file mode 100644 index 000000000000..ed29990daca9 --- /dev/null +++ b/app/util/onboarding/index.test.ts @@ -0,0 +1,90 @@ +import { shouldShowSmartTransactionsOptInModal } from './index'; +import StorageWrapper from '../../store/storage-wrapper'; +import { NETWORKS_CHAIN_ID } from '../../constants/network'; +import { store } from '../../store'; + +const getMockState = (optInModalAppVersionSeen: string | null) => ({ + smartTransactions: { + optInModalAppVersionSeen, + }, +}); + +jest.mock('../../store/storage-wrapper'); + +jest.mock('../../store', () => ({ + store: { + getState: jest.fn(() => getMockState(null)), + dispatch: jest.fn(), + }, +})); + +describe('shouldShowSmartTransactionOptInModal', () => { + beforeEach(() => { + // Clear all instances and calls to constructor and all methods: + (StorageWrapper.getItem as jest.Mock).mockClear(); + (store.getState as jest.Mock).mockClear(); + }); + + it('returns true if a user has not seen the modal, is on Ethereum mainnet with default RPC URL and has non-zero balance', async () => { + (StorageWrapper.getItem as jest.Mock).mockResolvedValueOnce('7.24.0'); // currentAppVersion + (store.getState as jest.Mock).mockReturnValueOnce(getMockState(null)); // versionSeen + + const result = await shouldShowSmartTransactionsOptInModal( + NETWORKS_CHAIN_ID.MAINNET, + undefined, + false, + ); + expect(result).toBe(true); + }); + + test.each([ + [NETWORKS_CHAIN_ID.MAINNET, 'http://mainnet-url.example.com'], + [NETWORKS_CHAIN_ID.ARBITRUM, 'http://arbitrum-url.example.com'], + ])( + `returns false if chainId is not ${NETWORKS_CHAIN_ID.MAINNET} or providerConfigRpcUrl is defined`, + async (chainId, rpcUrl) => { + const result = await shouldShowSmartTransactionsOptInModal( + chainId, + rpcUrl, + false, + ); + expect(result).toBe(false); + }, + ); + + it('returns false if user has seen the modal', async () => { + (StorageWrapper.getItem as jest.Mock).mockResolvedValueOnce('7.24.0'); // currentAppVersion + (store.getState as jest.Mock).mockReturnValueOnce(getMockState('7.24.0')); // versionSeen + + const result = await shouldShowSmartTransactionsOptInModal( + NETWORKS_CHAIN_ID.MAINNET, + undefined, + false, + ); + expect(result).toBe(false); + }); + + it('returns false if app version is not correct', async () => { + (StorageWrapper.getItem as jest.Mock).mockResolvedValueOnce('7.0.0'); // currentAppVersion + (store.getState as jest.Mock).mockReturnValueOnce(getMockState(null)); // versionSeen + + const result = await shouldShowSmartTransactionsOptInModal( + NETWORKS_CHAIN_ID.MAINNET, + undefined, + false, + ); + expect(result).toBe(false); + }); + + it('returns false if a user has 0 balance on Ethereum Mainnet with default RPC URL', async () => { + (StorageWrapper.getItem as jest.Mock).mockResolvedValueOnce('7.24.0'); // currentAppVersion + (store.getState as jest.Mock).mockReturnValueOnce(getMockState(null)); // versionSeen + + const result = await shouldShowSmartTransactionsOptInModal( + NETWORKS_CHAIN_ID.MAINNET, + undefined, + true, + ); + expect(result).toBe(false); + }); +}); diff --git a/app/util/onboarding/index.ts b/app/util/onboarding/index.ts index 62508c412c8b..3afd4ad6a6ce 100644 --- a/app/util/onboarding/index.ts +++ b/app/util/onboarding/index.ts @@ -7,12 +7,59 @@ import { } from '../../constants/storage'; import { whatsNewList } from '../../components/UI/WhatsNewModal'; import StorageWrapper from '../../store/storage-wrapper'; +import { NETWORKS_CHAIN_ID } from '../../constants/network'; +import { store } from '../../store'; const isVersionSeenAndGreaterThanMinAppVersion = ( versionSeen: string | null, minAppVersion: string, ) => !!versionSeen && compareVersions.compare(versionSeen, minAppVersion, '>='); +const STX_OPT_IN_MIN_APP_VERSION = '7.24.0'; + +/** + * + * @param chainId The chainId of the current network + * @param providerConfigRpcUrl The RPC URL of the current network + * @returns Boolean indicating whether or not to show smart transactions opt in modal + */ +export const shouldShowSmartTransactionsOptInModal = async ( + chainId: string, + providerConfigRpcUrl: string | undefined, + accountHasZeroBalance: boolean, +) => { + if ( + process.env.IS_TEST === 'true' || + chainId !== NETWORKS_CHAIN_ID.MAINNET || + providerConfigRpcUrl !== undefined || // undefined is the default RPC URL (Infura). + accountHasZeroBalance + ) { + return false; + } + + const versionSeen = + store.getState().smartTransactions.optInModalAppVersionSeen; + + const currentAppVersion = await StorageWrapper.getItem(CURRENT_APP_VERSION); + + // Check if user has seen + const seen = isVersionSeenAndGreaterThanMinAppVersion( + versionSeen, + STX_OPT_IN_MIN_APP_VERSION, + ); + + if (seen) return false; + + // Check version + const versionCorrect = compareVersions.compare( + currentAppVersion as string, + STX_OPT_IN_MIN_APP_VERSION, + '>=', + ); + + return versionCorrect; +}; + /** * Returns boolean indicating whether or not to show whats new modal * diff --git a/app/util/sentry/__snapshots__/utils.test.ts.snap b/app/util/sentry/__snapshots__/utils.test.ts.snap index 5155459d5e27..8b31961472f8 100644 --- a/app/util/sentry/__snapshots__/utils.test.ts.snap +++ b/app/util/sentry/__snapshots__/utils.test.ts.snap @@ -197,6 +197,9 @@ exports[`captureSentryFeedback maskObject masks initial root state fixture 1`] = "useBlockieIcon": true, }, "signatureRequest": "object", + "smartTransactions": { + "optInModalAppVersionSeen": null, + }, "swaps": "object", "transaction": "object", "transactionMetrics": "object", diff --git a/app/util/sentry/tags/index.test.ts b/app/util/sentry/tags/index.test.ts index b6f5808ef5aa..5511ab7cf928 100644 --- a/app/util/sentry/tags/index.test.ts +++ b/app/util/sentry/tags/index.test.ts @@ -225,130 +225,5 @@ describe('Tags Utils', () => { expect(tags?.['wallet.transaction_count']).toStrictEqual(3); }); - - it('returns undefined if ApprovalController is not defined', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - ApprovalController: undefined, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeUndefined(); - }); - - it('handles undefined pendingApprovals in ApprovalController', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - ApprovalController: { - ...backgroundState.ApprovalController, - pendingApprovals: undefined, - }, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags?.['wallet.pending_approval']).toBeUndefined(); - }); - - it('handles ApprovalController as an empty object', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - ApprovalController: {}, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeDefined(); - expect(tags?.['wallet.pending_approval']).toBeUndefined(); - }); - - it('returns undefined if NftController.allNfts is not defined', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - NftController: { - ...backgroundState.NftController, - allNfts: undefined, - }, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeUndefined(); - }); - - it('returns undefined if NotificationServicesController.metamaskNotificationsList is not defined', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - NotificationServicesController: { - metamaskNotificationsList: undefined, - }, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeUndefined(); - }); - - it('returns undefined if TokensController.allTokens is not defined', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - TokensController: { - allTokens: undefined, - }, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeUndefined(); - }); - - it('returns undefined if TransactionController.transactions is not defined', () => { - const state = { - ...initialRootState, - engine: { - backgroundState: { - ...backgroundState, - TransactionController: { - transactions: undefined, - }, - }, - }, - } as unknown as RootState; - - const tags = getTraceTags(state); - - expect(tags).toBeUndefined(); - }); }); }); diff --git a/app/util/sentry/tags/index.ts b/app/util/sentry/tags/index.ts index 7d0a2ceabf2d..41e5c395180d 100644 --- a/app/util/sentry/tags/index.ts +++ b/app/util/sentry/tags/index.ts @@ -9,28 +9,19 @@ import { selectPendingApprovals } from '../../../selectors/approvalController'; export function getTraceTags(state: RootState) { if (!state?.engine?.backgroundState?.AccountsController) return; if (!state?.engine?.backgroundState?.NftController) return; - if (!state?.engine?.backgroundState?.NftController?.allNfts) return; if (!state?.engine?.backgroundState?.NotificationServicesController) return; - if ( - !state?.engine?.backgroundState?.NotificationServicesController - ?.metamaskNotificationsList - ) - return; if (!state?.engine?.backgroundState?.TokensController) return; - if (!state?.engine?.backgroundState?.TokensController?.allTokens) return; if (!state?.engine?.backgroundState?.TransactionController) return; - if (!state?.engine?.backgroundState?.TransactionController?.transactions) - return; + if (!state?.engine?.backgroundState?.NotificationServicesController) return; if (!state?.engine?.backgroundState?.ApprovalController) return; - if (!Object.keys(state?.engine?.backgroundState).length) return; const unlocked = state.user.userLoggedIn; - const accountCount = selectInternalAccounts(state)?.length; - const nftCount = selectAllNftsFlat(state)?.length; - const notificationCount = getNotificationsList(state)?.length; - const tokenCount = selectAllTokensFlat(state)?.length; - const transactionCount = selectTransactions(state)?.length; + const accountCount = selectInternalAccounts(state).length; + const nftCount = selectAllNftsFlat(state).length; + const notificationCount = getNotificationsList(state).length; + const tokenCount = selectAllTokensFlat(state).length; + const transactionCount = selectTransactions(state).length; const pendingApprovals = selectPendingApprovals(state); const pendingApprovalsValues = Object.values(pendingApprovals ?? {}); diff --git a/app/util/sentry/utils.test.ts b/app/util/sentry/utils.test.ts index 6f6902893794..1c4a0b9c02ac 100644 --- a/app/util/sentry/utils.test.ts +++ b/app/util/sentry/utils.test.ts @@ -366,6 +366,9 @@ describe('captureSentryFeedback', () => { type: undefined, warningGasPriceHigh: undefined, }, + smartTransactions: { + optInModalAppVersionSeen: null, + }, user: { ambiguousAddressEntries: {}, backUpSeedphraseVisible: false, @@ -497,6 +500,7 @@ describe('captureSentryFeedback', () => { security: 'object', settings: 'object', signatureRequest: 'object', + smartTransactions: 'object', swaps: 'object', transaction: 'object', transactionMetrics: 'object', @@ -536,6 +540,7 @@ describe('captureSentryFeedback', () => { security: 'object', settings: 'object', signatureRequest: 'object', + smartTransactions: 'object', swaps: 'object', transaction: 'object', transactionMetrics: 'object', @@ -572,6 +577,7 @@ describe('captureSentryFeedback', () => { security: 'object', settings: 'object', signatureRequest: 'object', + smartTransactions: 'object', swaps: 'object', transaction: 'object', transactionMetrics: 'object', diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts index 2131439442ee..40d3fa1c27c4 100644 --- a/app/util/test/confirm-data-helpers.ts +++ b/app/util/test/confirm-data-helpers.ts @@ -33,42 +33,3 @@ export const personalSignatureConfirmationState = { }, }, }; - -export const typedSignV1ConfirmationState = { - engine: { - backgroundState: { - ...backgroundState, - ApprovalController: { - pendingApprovals: { - '7e62bcb1-a4e9-11ef-9b51-ddf21c91a998': { - id: '7e62bcb1-a4e9-11ef-9b51-ddf21c91a998', - origin: 'metamask.github.io', - type: 'eth_signTypedData', - time: 1731850822653, - requestData: { - data: [ - { type: 'string', name: 'Message', value: 'Hi, Alice!' }, - { type: 'uint32', name: 'A number', value: '1337' }, - ], - from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', - requestId: 2453610887, - meta: { - url: 'https://metamask.github.io/test-dapp/', - title: 'E2E Test Dapp', - icon: { uri: 'https://metamask.github.io/metamask-fox.svg' }, - analytics: { request_source: 'In-App-Browser' }, - }, - origin: 'metamask.github.io', - metamaskId: '7e62bcb0-a4e9-11ef-9b51-ddf21c91a998', - version: 'V1', - }, - requestState: null, - expectsResult: true, - }, - }, - pendingApprovalCount: 1, - approvalFlows: [], - }, - }, - }, -}; diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 1c7492d5c450..5e26fc63afb1 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -190,7 +190,7 @@ "0x64": true }, "isIpfsGatewayEnabled": true, - "smartTransactionsOptInStatus": true, + "smartTransactionsOptInStatus": false, "useTransactionSimulations": true, "tokenSortConfig": { "key": "tokenFiatAmount", diff --git a/app/util/test/initial-root-state.ts b/app/util/test/initial-root-state.ts index 58be6fea8d85..a14f5fec8e68 100644 --- a/app/util/test/initial-root-state.ts +++ b/app/util/test/initial-root-state.ts @@ -3,6 +3,7 @@ import type { EngineState } from '../../core/Engine'; import { initialState as initialFiatOrdersState } from '../../reducers/fiatOrders'; import { initialState as initialSecurityState } from '../../reducers/security'; import { initialState as initialInpageProvider } from '../../core/redux/slices/inpageProvider'; +import { initialState as initialSmartTransactions } from '../../core/redux/slices/smartTransactions'; import { initialState as transactionMetrics } from '../../core/redux/slices/transactionMetrics'; import { initialState as originThrottling } from '../../core/redux/slices/originThrottling'; import { initialState as initialFeatureFlagsState } from '../../core/redux/slices/featureFlags'; @@ -26,6 +27,7 @@ const initialRootState: RootState = { settings: undefined, alert: undefined, transaction: undefined, + smartTransactions: initialSmartTransactions, user: userInitialState, wizard: undefined, onboarding: undefined, diff --git a/bitrise.yml b/bitrise.yml index c2c128b9ebde..e28cbb062f1c 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1576,16 +1576,16 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.35.0 + VERSION_NAME: 7.36.0 - opts: is_expand: false - VERSION_NUMBER: 1497 + VERSION_NUMBER: 1501 - opts: is_expand: false - FLASK_VERSION_NAME: 7.35.0 + FLASK_VERSION_NAME: 7.36.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1497 + FLASK_VERSION_NUMBER: 1501 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/e2e/api-mocking/mock-config/mock-events.js b/e2e/api-mocking/mock-config/mock-events.js index 0d6398124978..acfd95a6fdf3 100644 --- a/e2e/api-mocking/mock-config/mock-events.js +++ b/e2e/api-mocking/mock-config/mock-events.js @@ -22,7 +22,7 @@ export const mockEvents = { response: suggestedGasApiResponses.error, responseCode: 500, }, - + /** * Ganache gas fees endpoint with a mock 200 success response. * @property {string} urlEndpoint - API endpoint for Ganache gas fees. diff --git a/e2e/pages/Settings/AesCryptoTestForm.js b/e2e/pages/Settings/AesCryptoTestForm.js index d71289ce6c99..f21637d2aa67 100644 --- a/e2e/pages/Settings/AesCryptoTestForm.js +++ b/e2e/pages/Settings/AesCryptoTestForm.js @@ -203,7 +203,6 @@ class AesCryptoTestForm { this.decryptPasswordInput, encryptionKey, ); - await this.scrollToDecrypt(); await Gestures.waitAndTap(this.decryptButton); } diff --git a/e2e/pages/Transactions/TransactionProtectionModal.js b/e2e/pages/Transactions/TransactionProtectionModal.js new file mode 100644 index 000000000000..250ac4239b87 --- /dev/null +++ b/e2e/pages/Transactions/TransactionProtectionModal.js @@ -0,0 +1,23 @@ +import { TransactionProtectionModalSelectorText } from '../../selectors/Transactions/TransactionProtectionModal.selectors'; +import Matchers from '../../utils/Matchers'; +import Gestures from '../../utils/Gestures'; + +class TransactionProtectionModal { + get header() { + return Matchers.getElementByText( + TransactionProtectionModalSelectorText.HEADER, + ); + } + + get enableButton() { + return Matchers.getElementByText( + TransactionProtectionModalSelectorText.ENABLE_BUTTON, + ); + } + + async tapEnableButton() { + await Gestures.waitAndTap(this.enableButton); + } +} + +export default new TransactionProtectionModal(); diff --git a/e2e/selectors/Modals/SmartTransactionsOptInModal.selectors.js b/e2e/selectors/Modals/SmartTransactionsOptInModal.selectors.js new file mode 100644 index 000000000000..449c585d83b5 --- /dev/null +++ b/e2e/selectors/Modals/SmartTransactionsOptInModal.selectors.js @@ -0,0 +1,4 @@ +export const SmartTransactionsOptInModalSelectorsIDs = { + CLOSE_BUTTON: 'smart-transactions-opt-in-modal-close-button', + CONTAINER: 'smart-transactions-opt-in-modal-container', +}; diff --git a/e2e/selectors/Transactions/TransactionProtectionModal.selectors.js b/e2e/selectors/Transactions/TransactionProtectionModal.selectors.js new file mode 100644 index 000000000000..98978e8f6a85 --- /dev/null +++ b/e2e/selectors/Transactions/TransactionProtectionModal.selectors.js @@ -0,0 +1,6 @@ +import enContent from '../../../locales/languages/en.json'; + +export const TransactionProtectionModalSelectorText = { + HEADER: enContent.whats_new.stx.header, + ENABLE_BUTTON: enContent.whats_new.stx.primary_button, +}; diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 3d2ddd98edd4..ce2c2c0a1767 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1281,7 +1281,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1318,7 +1318,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1346,7 +1346,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1381,7 +1381,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1409,7 +1409,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1442,7 +1442,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1470,7 +1470,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1501,7 +1501,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1661,7 +1661,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1497; + CURRENT_PROJECT_VERSION = 1501; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1727,7 +1727,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.35.0; + MARKETING_VERSION = 7.36.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/locales/languages/de.json b/locales/languages/de.json index 7c02a371a666..dffdebd14f5e 100644 --- a/locales/languages/de.json +++ b/locales/languages/de.json @@ -2940,6 +2940,20 @@ "description": "Verkaufen Sie Ihre Krypto direkt in MetaMask! Wir helfen Ihnen, Kurse von vertrauenswürdigen Anbietern zu finden, damit Sie Ihre Krypto jederzeit zugreifbar, schnell und sicher verkaufen können.", "action_text": "Probieren Sie es aus" }, + "stx": { + "header": "Verbesserter Transaktionsschutz", + "description_1": "Erzielen Sie mit Smart Transactions höhere Erfolgsraten, einen Frontrunning-Schutz und eine bessere Transparenz.", + "description_2": "Nur auf Ethereum verfügbar. Sie können diese Funktion jederzeit in den Einstellungen aktivieren oder deaktivieren.", + "no_thanks": "Nein, danke", + "primary_button": "Aktivieren", + "learn_more": "Erfahren Sie mehr.", + "benefit_1_1": "Erfolgsrate:", + "benefit_1_2": "99,5 %", + "benefit_2_1": "Spart Ihnen", + "benefit_2_2": "Geld", + "benefit_3_1": "Echtzeit", + "benefit_3_2": "Updates" + }, "transaction_simulation": { "title": "Geschätzte Saldoänderungen", "description_1": "Jetzt können Sie das mögliche Ergebnis Ihrer Transaktionen sehen, bevor Sie sie tätigen!", diff --git a/locales/languages/el.json b/locales/languages/el.json index 3534e6293471..9e21703a317b 100644 --- a/locales/languages/el.json +++ b/locales/languages/el.json @@ -2940,6 +2940,20 @@ "description": "Πουλήστε τα κρυπτονομίσματά σας απευθείας στο MetaMask! Θα σας βοηθήσουμε να βρείτε προσφορές από αξιόπιστους παρόχους, ώστε να μπορείτε να πουλήσετε τα κρυπτονομίσματά σας με προσιτό, γρήγορο και ασφαλή τρόπο, κάθε φορά.", "action_text": "Δοκιμάστε το" }, + "stx": { + "header": "Ενισχυμένη Προστασία Συναλλαγών", + "description_1": "Ξεκλειδώστε υψηλότερα ποσοστά επιτυχίας, προστασία εκ των προτέρων και καλύτερη ορατότητα με τις Έξυπνες Συναλλαγές.", + "description_2": "Διατίθεται μόνο στο Ethereum. Ενεργοποίηση ή απενεργοποίηση ανά πάσα στιγμή στις ρυθμίσεις.", + "no_thanks": "Όχι, ευχαριστώ", + "primary_button": "Ενεργοποίηση", + "learn_more": "Μάθετε περισσότερα.", + "benefit_1_1": "99,5% ποσοστό", + "benefit_1_2": "επιτυχίας", + "benefit_2_1": "Σας εξοικονομεί", + "benefit_2_2": "χρήματα", + "benefit_3_1": "Ενημερώσεις", + "benefit_3_2": "σε πραγματικό χρόνο" + }, "transaction_simulation": { "title": "Εκτιμώμενες μεταβολές υπολοίπου", "description_1": "Τώρα μπορείτε να βλέπετε την πιθανή απόδοση των συναλλαγών σας πριν τις πραγματοποιήσετε!", diff --git a/locales/languages/en.json b/locales/languages/en.json index 490e20b7f2aa..9c371ef2bde7 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -2963,6 +2963,20 @@ "description": "Sell your crypto directly in MetaMask! We’ll help you find quotes from trusted providers, so you can sell your crypto in an accessible, fast, and secure way, every time.", "action_text": "Try it out" }, + "stx": { + "header": "Enhanced Transaction Protection", + "description_1": "Unlock higher success rates, frontrunning protection, and better visibility with Smart Transactions.", + "description_2": "Only available on Ethereum. Enable or disable any time in settings.", + "no_thanks": "No thanks", + "primary_button": "Enable", + "learn_more": "Learn more.", + "benefit_1_1": "99.5% success", + "benefit_1_2": "rate", + "benefit_2_1": "Saves you", + "benefit_2_2": "money", + "benefit_3_1": "Real-time", + "benefit_3_2": "updates" + }, "transaction_simulation": { "title": "Estimated balance changes", "description_1": "Now you can see the potential outcome of your transactions before you make them!", @@ -3352,8 +3366,7 @@ "blind_sign_error": "Blind signing error", "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings.", "user_reject_transaction": "User rejected the transaction", - "user_reject_transaction_message": "The user has rejected the transaction on the Ledger device.", - "multiple_devices_error_message": "Multiple devices aren’t supported yet. To add a new Ledger device, you’ll need to remove an older one." + "user_reject_transaction_message": "The user has rejected the transaction on the Ledger device." }, "account_actions": { "edit_name": "Edit account name", @@ -3599,11 +3612,5 @@ "description": "Estimated changes for this transaction have been updated. Review them closely before proceeding.", "proceed": "Proceed", "reject": "Reject the transaction" - }, - "snap_account_custom_name_approval": { - "title": "Add account to MetaMask", - "input_title": "Account name", - "add_account_button": "Add account", - "name_taken_message": "This account name already exists" } } diff --git a/locales/languages/es.json b/locales/languages/es.json index 77d8ec3da9c8..a3dd4f406510 100644 --- a/locales/languages/es.json +++ b/locales/languages/es.json @@ -2940,6 +2940,20 @@ "description": "¡Venda su cripto directamente en MetaMask! Lo ayudaremos a encontrar cotizaciones de proveedores confiables, para que pueda vender su cripto de manera accesible, rápida y segura, en todo momento.", "action_text": "Inténtelo" }, + "stx": { + "header": "Protección mejorada de transacciones", + "description_1": "Desbloquee índices de éxito más altos, protección contra frontrunning y mejor visibilidad con transacciones inteligentes.", + "description_2": "Solo disponible en Ethereum. Active o desactive en cualquier momento en la configuración.", + "no_thanks": "No, gracias", + "primary_button": "Activar", + "learn_more": "Más información.", + "benefit_1_1": "99,5 % de índice", + "benefit_1_2": "de éxito", + "benefit_2_1": "Le permite ahorrar", + "benefit_2_2": "dinero", + "benefit_3_1": "Tiempo real", + "benefit_3_2": "actualizaciones" + }, "transaction_simulation": { "title": "Cambios de saldo estimados", "description_1": "¡Ahora puede ver el resultado potencial de sus transacciones antes de realizarlas!", diff --git a/locales/languages/fr.json b/locales/languages/fr.json index 6639e3d0060d..e97d53f27fc1 100644 --- a/locales/languages/fr.json +++ b/locales/languages/fr.json @@ -2940,6 +2940,20 @@ "description": "Vendez vos cryptomonnaies directement dans MetaMask ! Nous vous aiderons à trouver des cotations auprès de fournisseurs de confiance, afin que vous puissiez vendre rapidement et en toute sécurité vos cryptomonnaies.", "action_text": "Essayez cette fonctionnalité" }, + "stx": { + "header": "Protection renforcée des transactions", + "description_1": "Bénéficiez de taux de réussite plus élevés, d’une protection contre le « front running » et d’une meilleure visibilité grâce aux transactions intelligentes.", + "description_2": "Disponible uniquement sur Ethereum. Vous pouvez activer ou désactiver cette option à tout moment dans les paramètres.", + "no_thanks": "Non, merci", + "primary_button": "Activer", + "learn_more": "En savoir plus.", + "benefit_1_1": "Taux de réussite de", + "benefit_1_2": "99,5 %", + "benefit_2_1": "Vous fait économiser", + "benefit_2_2": "de l’argent", + "benefit_3_1": "Mises à jour", + "benefit_3_2": "en temps réel" + }, "transaction_simulation": { "title": "Estimation des changements de solde", "description_1": "Vous pouvez désormais connaître le résultat potentiel de vos transactions avant de les effectuer !", diff --git a/locales/languages/hi.json b/locales/languages/hi.json index bb5575486cf7..bb30b92c6dc1 100644 --- a/locales/languages/hi.json +++ b/locales/languages/hi.json @@ -2940,6 +2940,20 @@ "description": "अपना क्रिप्टो सीधे MetaMask में बेचें! हम आपको भरोसेमंद प्रोवाइडर्स से कोटेशन पाने में मदद करेंगे, ताकि आप हर बार अपने क्रिप्टो को एक्सेस किए जा सकने वाले, तेज़ और सुरक्षित तरीके से बेच सकें।", "action_text": "इसे आज़माकर देखें" }, + "stx": { + "header": "उन्नत ट्रांसेक्शन सुरक्षा", + "description_1": "स्मार्ट ट्रांसेक्शन के साथ उच्च सफलता दर, फ्रंटरनिंग सुरक्षा और बेहतर दृश्यता अनलॉक करें।", + "description_2": "केवल Ethereum पर उपलब्ध है। सेटिंग्स में किसी भी समय चालू करें या बंद करें।", + "no_thanks": "जी नहीं, धन्यवाद", + "primary_button": "चालू करें", + "learn_more": "अधिक जानें।", + "benefit_1_1": "99.5% सफलता", + "benefit_1_2": "दर", + "benefit_2_1": "आपका पैसा", + "benefit_2_2": "बचाता है", + "benefit_3_1": "रियल टाइम", + "benefit_3_2": "अपडेट" + }, "transaction_simulation": { "title": "अनुमानित बैलेंस अमाउंट में बदलाव", "description_1": "अब आप अपने ट्रांसेक्शन करने से पहले उनका पोटेंशियल आउटकम देख सकते हैं!", diff --git a/locales/languages/id.json b/locales/languages/id.json index d48afddc7d61..df78ae5c2eb0 100644 --- a/locales/languages/id.json +++ b/locales/languages/id.json @@ -2940,6 +2940,20 @@ "description": "Jual kripto langsung di MetaMask! Kami akan membantu menemukan penawaran harga dari penyedia tepercaya, sehingga Anda dapat menjual kripto dengan cara yang mudah diakses, cepat, dan aman, setiap saat.", "action_text": "Cobalah" }, + "stx": { + "header": "Peningkatan Perlindungan Transaksi", + "description_1": "Raih tingkat keberhasilan yang lebih tinggi, perlindungan frontrunning, dan visibilitas yang lebih baik dengan Transaksi Pintar.", + "description_2": "Hanya tersedia di Ethereum. Aktifkan atau nonaktifkan setiap saat di pengaturan.", + "no_thanks": "Tidak, terima kasih", + "primary_button": "Aktifkan", + "learn_more": "Selengkapnya.", + "benefit_1_1": "Keberhasilan 99,5%", + "benefit_1_2": "tingkat", + "benefit_2_1": "Menghemat", + "benefit_2_2": "uang", + "benefit_3_1": "Waktu nyata", + "benefit_3_2": "pembaruan" + }, "transaction_simulation": { "title": "Estimasi perubahan saldo", "description_1": "Kini Anda dapat melihat potensi hasil transaksi sebelum melakukannya!", diff --git a/locales/languages/ja.json b/locales/languages/ja.json index 38fa89f78e4d..494027431d10 100644 --- a/locales/languages/ja.json +++ b/locales/languages/ja.json @@ -2940,6 +2940,20 @@ "description": "MetaMaskで直接仮想通貨を売却!信頼できるプロバイダーからのクォートを取得して、毎回簡単・迅速・安全に仮想通貨を売却できます。", "action_text": "お試しください" }, + "stx": { + "header": "強化されたトランザクション保護", + "description_1": "スマートトランザクションで、成功率を上げ、フロントランニングを防ぎ、可視性を高めましょう。", + "description_2": "イーサリアムでのみご利用いただけ、いつでも設定で有効・無効を切り替えられます。", + "no_thanks": "いいえ、結構です", + "primary_button": "有効にする", + "learn_more": "詳細。", + "benefit_1_1": "99.5%の", + "benefit_1_2": "成功率", + "benefit_2_1": "お金を", + "benefit_2_2": "節約できます", + "benefit_3_1": "リアルタイム", + "benefit_3_2": "最新情報" + }, "transaction_simulation": { "title": "予測される残高の増減", "description_1": "トランザクションを実行する前に、予測される結果を確認できるようになりました!", diff --git a/locales/languages/ko.json b/locales/languages/ko.json index d3ca23a79aeb..9ce269e22d9f 100644 --- a/locales/languages/ko.json +++ b/locales/languages/ko.json @@ -2940,6 +2940,20 @@ "description": "MetaMask에서 바로 암호화폐를 매도하세요! 신뢰할 수 있는 제공업체의 견적을 찾을 수 있도록 도와드립니다. 언제나 쉽고 빠르며 안전한 방법으로 암호화폐를 매도할 수 있습니다.", "action_text": "사용해 보세요" }, + "stx": { + "header": "트랜잭션 보호 강화", + "description_1": "스마트 트랜잭션으로 선행거래를 방지하고 더 높은 성공률과 가시성을 확보하세요.", + "description_2": "이더리움에서만 사용할 수 있습니다. 설정에서 언제든지 활성화하거나 비활성화할 수 있습니다.", + "no_thanks": "아니요, 괜찮습니다", + "primary_button": "활성화", + "learn_more": "자세히 알아보세요.", + "benefit_1_1": "99.5% 성공", + "benefit_1_2": "률", + "benefit_2_1": "비용", + "benefit_2_2": "절감", + "benefit_3_1": "실시간", + "benefit_3_2": "업데이트" + }, "transaction_simulation": { "title": "예상 잔액 변동", "description_1": "이제 트랜잭션 전에 예상 결과를 확인할 수 있습니다!", diff --git a/locales/languages/pt.json b/locales/languages/pt.json index bdffcfbb85bb..d449ba5434d5 100644 --- a/locales/languages/pt.json +++ b/locales/languages/pt.json @@ -2940,6 +2940,20 @@ "description": "Venda seus criptoativos diretamente na MetaMask! Vamos ajudar você a encontrar cotações de provedores de confiança para que possa vendê-los de forma acessível, rápida e segura, sempre.", "action_text": "Experimente" }, + "stx": { + "header": "Proteção de transações aprimorada", + "description_1": "Desbloqueie taxas de sucesso maiores, proteção contra front running e melhor visibilidade com as transações inteligentes.", + "description_2": "Disponível somente na Ethereum. Ative ou desative a qualquer momento nas configurações.", + "no_thanks": "Não, obrigado", + "primary_button": "Ativar", + "learn_more": "Saiba mais.", + "benefit_1_1": "99,5% de taxa de", + "benefit_1_2": "sucesso", + "benefit_2_1": "Faz você economizar", + "benefit_2_2": "dinheiro", + "benefit_3_1": "em tempo real", + "benefit_3_2": "Atualizações" + }, "transaction_simulation": { "title": "Alterações de saldo estimadas", "description_1": "Agora você pode ver os possíveis resultados de suas transações antes de realizá-las!", diff --git a/locales/languages/ru.json b/locales/languages/ru.json index c3e0c38e8fb1..0b38da80fcc2 100644 --- a/locales/languages/ru.json +++ b/locales/languages/ru.json @@ -2940,6 +2940,20 @@ "description": "Продавайте свою криптовалюту прямо в MetaMask! Мы поможем вам найти котировки от надежных поставщиков, чтобы вы всегда могли продавать свою криптовалюту доступным, быстрым и безопасным способом.", "action_text": "Попробовать" }, + "stx": { + "header": "Улучшенная защита транзакций", + "description_1": "Откройте для себя более высокие коэффициенты успеха, передовую защиту и лучшую прозрачность с помощью смарт-транзакций.", + "description_2": "Доступно только на Ethereum. Включайте или отключайте в любое время в настройках.", + "no_thanks": "Нет, спасибо", + "primary_button": "Включить", + "learn_more": "Подробнее.", + "benefit_1_1": "Показатель успеха:", + "benefit_1_2": "99,5 %", + "benefit_2_1": "Экономит вам", + "benefit_2_2": "деньги", + "benefit_3_1": "Статус", + "benefit_3_2": "в реальном времени" + }, "transaction_simulation": { "title": "Предполагаемые изменения баланса", "description_1": "Теперь вы можете увидеть потенциальный результат ваших транзакций еще до их совершения!", diff --git a/locales/languages/tl.json b/locales/languages/tl.json index f0588e1f4b85..70f54a9472af 100644 --- a/locales/languages/tl.json +++ b/locales/languages/tl.json @@ -2940,6 +2940,20 @@ "description": "Ibenta ang iyong crypto nang direkta sa MetaMask! Tutulungan ka naming maghanap ng mga quote mula sa mga mapagkakatiwalaang provider, nang sa gayon ay maibenta mo ang iyong crypto sa isang accessible, mabilis, at secure na paraan, sa bawat pagkakataon.", "action_text": "Subukan ito" }, + "stx": { + "header": "Pinahusay na Proteksyon sa Transaksyon", + "description_1": "I-unlock ang mas mataas na tiyansa ng tagumpay, proteksyon sa frontrunning, at mas mahusay na visibility sa mga Smart Transaction.", + "description_2": "Available lamang sa Ethereum. I-enable o i-disable anumang oras sa mga setting.", + "no_thanks": "Salamat na lang", + "primary_button": "I-enable", + "learn_more": "Matuto pa.", + "benefit_1_1": "99.5% tagumpay", + "benefit_1_2": "rate", + "benefit_2_1": "Nakakatipid ng", + "benefit_2_2": "pera", + "benefit_3_1": "Real-time", + "benefit_3_2": "mga update" + }, "transaction_simulation": { "title": "Tinatayang mga pagbabago sa balanse", "description_1": "Maaari mo na ngayong makita ang potensyal na resulta ng iyong mga transaksyon bago mo gawin ang mga iyon!", diff --git a/locales/languages/tr.json b/locales/languages/tr.json index a4ac3edbd715..7c6a706d6f1f 100644 --- a/locales/languages/tr.json +++ b/locales/languages/tr.json @@ -2940,6 +2940,20 @@ "description": "Kriptonuzu doğrudan MetaMask'ta satın! Kriptonuzu her defasında erişilebilir, hızlı ve güvenli bir şekilde satabilmeniz için güvenilir sağlayıcılardan teklifleri bulmanıza yardımcı olacağız.", "action_text": "Deneyin" }, + "stx": { + "header": "İyileştirilmiş İşlem Koruması", + "description_1": "Akıllı İşlemler ile daha yüksek başarı oranlarının, arkadan çalıştırma korumasının ve daha iyi görünürlüğün kilidini açın.", + "description_2": "Sadece Ethereum'da mevcuttur. Dilediğiniz zaman ayarlar kısmında etkinleştirin veya devre dışı bırakın.", + "no_thanks": "Hayır, teşekkürler", + "primary_button": "Etkinleştir", + "learn_more": "Daha fazla bilgi edinin.", + "benefit_1_1": "%99,5 başarı oranı", + "benefit_1_2": "oran", + "benefit_2_1": "Paradan", + "benefit_2_2": "tasarruf sağlar", + "benefit_3_1": "Gerçek zamanlı", + "benefit_3_2": "güncellemeler" + }, "transaction_simulation": { "title": "Tahmini bakiye değişiklikleri", "description_1": "Artık gerçekleştirmeden önce işlemlerinizin potansiyel sonuçlarını görebilirsiniz!", diff --git a/locales/languages/vi.json b/locales/languages/vi.json index f065c68b4a0f..629f43753361 100644 --- a/locales/languages/vi.json +++ b/locales/languages/vi.json @@ -2940,6 +2940,20 @@ "description": "Bán tiền mã hóa trực tiếp trong MetaMask! Chúng tôi sẽ giúp bạn tìm báo giá từ các nhà cung cấp đáng tin cậy, qua đó bạn có thể bán tiền mã hóa mọi lúc một cách dễ dàng, nhanh chóng và an toàn.", "action_text": "Thử ngay" }, + "stx": { + "header": "Tăng cường bảo vệ giao dịch", + "description_1": "Đạt tỷ lệ thành công cao hơn, bảo vệ chống hành vi lợi dụng thông tin biết trước và khả năng hiển thị tốt hơn với Giao dịch thông minh.", + "description_2": "Chỉ có sẵn trên Ethereum. Có thể bật/tắt bất cứ lúc nào trong phần Cài đặt.", + "no_thanks": "Không, cảm ơn", + "primary_button": "Bật", + "learn_more": "Tìm hiểu thêm.", + "benefit_1_1": "Tỷ lệ thành công", + "benefit_1_2": "99,5%", + "benefit_2_1": "Tiết kiệm tiền", + "benefit_2_2": "của bạn", + "benefit_3_1": "Cập nhật", + "benefit_3_2": "theo thời gian thực" + }, "transaction_simulation": { "title": "Thay đổi số dư ước tính", "description_1": "Bây giờ bạn có thể xem kết quả tiềm năng của các giao dịch trước khi thực hiện!", diff --git a/locales/languages/zh.json b/locales/languages/zh.json index 8370f2577e70..519544cb79fe 100644 --- a/locales/languages/zh.json +++ b/locales/languages/zh.json @@ -2940,6 +2940,20 @@ "description": "直接在 MetaMask 卖出您的加密货币!我们将帮助您从值得信赖的提供商处获取报价,以便您每次都能够以便捷、快速、安全的方式卖出您的加密货币。", "action_text": "试试看吧" }, + "stx": { + "header": "增强型交易保护", + "description_1": "通过智能交易解锁更高的成功率、抢先交易保护和更高的透明度。", + "description_2": "仅适用于以太坊。可随时在设置中启用或禁用。", + "no_thanks": "不,谢谢", + "primary_button": "启用", + "learn_more": "了解更多。", + "benefit_1_1": "99.5% 的成功", + "benefit_1_2": "率", + "benefit_2_1": "为您省", + "benefit_2_2": "钱", + "benefit_3_1": "实时", + "benefit_3_2": "更新" + }, "transaction_simulation": { "title": "预计余额变化", "description_1": "现在,您可以在进行交易之前看到交易的潜在结果!", diff --git a/package.json b/package.json index 29673ec40908..6f734cc00361 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.35.0", + "version": "7.36.0", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", @@ -140,7 +140,7 @@ "@metamask/accounts-controller": "^18.2.1", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^43.1.1", + "@metamask/assets-controllers": "^41.0.0", "@metamask/base-controller": "^7.0.1", "@metamask/composable-controller": "^3.0.0", "@metamask/controller-utils": "^11.3.0", @@ -168,7 +168,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.35.1", - "@metamask/preferences-controller": "^13.3.0", + "@metamask/preferences-controller": "^13.1.0", "@metamask/profile-sync-controller": "^0.9.7", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", @@ -178,7 +178,7 @@ "@metamask/rpc-errors": "^7.0.1", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/selected-network-controller": "^18.0.2", + "@metamask/selected-network-controller": "^15.0.2", "@metamask/signature-controller": "^21.0.0", "@metamask/slip44": "3.1.0", "@metamask/smart-transactions-controller": "^13.1.0", @@ -189,8 +189,8 @@ "@metamask/snaps-utils": "^8.1.1", "@metamask/stake-sdk": "^0.2.13", "@metamask/swappable-obj-proxy": "^2.1.0", - "@metamask/swaps-controller": "^10.0.0", - "@metamask/transaction-controller": "^39.1.0", + "@metamask/swaps-controller": "^9.0.12", + "@metamask/transaction-controller": "^38.3.0", "@metamask/utils": "^9.2.1", "@ngraveio/bc-ur": "^1.1.6", "@notifee/react-native": "^9.0.0", diff --git a/patches/@metamask+assets-controllers+41.0.0.patch b/patches/@metamask+assets-controllers+41.0.0.patch new file mode 100644 index 000000000000..876889a61997 --- /dev/null +++ b/patches/@metamask+assets-controllers+41.0.0.patch @@ -0,0 +1,1088 @@ +diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs +index aac84e5..983c3f1 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs +@@ -1,19 +1,19 @@ + "use strict"; +-var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { +- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); +- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); +- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +-}; + var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; + }; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; + var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; + }; +-var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; ++var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.AccountTrackerController = void 0; + const controller_utils_1 = require("@metamask/controller-utils"); +@@ -47,8 +47,10 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo + * @param options.interval - Polling interval used to fetch new account balances. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller messaging system. ++ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. ++ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. + */ +- constructor({ interval = 10000, state, messenger, }) { ++ constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) { + const { selectedNetworkClientId } = messenger.call('NetworkController:getState'); + const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId); + super({ +@@ -65,7 +67,11 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo + }); + _AccountTrackerController_instances.add(this); + _AccountTrackerController_refreshMutex.set(this, new async_mutex_1.Mutex()); ++ _AccountTrackerController_includeStakedAssets.set(this, void 0); ++ _AccountTrackerController_getStakedBalanceForChain.set(this, void 0); + _AccountTrackerController_handle.set(this, void 0); ++ __classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f"); ++ __classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f"); + this.setIntervalLength(interval); + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/no-floating-promises +@@ -171,6 +177,15 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo + balance, + }; + } ++ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { ++ const stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); ++ if (stakedBalance) { ++ accountsForChain[address] = { ++ ...accountsForChain[address], ++ stakedBalance, ++ }; ++ } ++ } + } + this.update((state) => { + if (chainId === __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getCurrentChainId).call(this)) { +@@ -196,18 +211,23 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo + return (0, controller_utils_1.safelyExecuteWithTimeout)(async () => { + (0, utils_1.assert)(ethQuery, 'Provider not set.'); + const balance = await (0, controller_utils_1.query)(ethQuery, 'getBalance', [address]); +- return [address, balance]; ++ let stakedBalance; ++ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { ++ stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); ++ } ++ return [address, balance, stakedBalance]; + }); + })).then((value) => { + return value.reduce((obj, item) => { + if (!item) { + return obj; + } +- const [address, balance] = item; ++ const [address, balance, stakedBalance] = item; + return { + ...obj, + [address]: { + balance, ++ stakedBalance, + }, + }; + }, {}); +@@ -215,7 +235,7 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo + } + } + exports.AccountTrackerController = AccountTrackerController; +-_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { ++_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { + const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState'); + const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); + return chainId; +diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts +index 144e018..6b7d8cd 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts +@@ -2,6 +2,7 @@ import type { AccountsControllerSelectedEvmAccountChangeEvent, AccountsControlle + import type { ControllerStateChangeEvent, ControllerGetStateAction, RestrictedControllerMessenger } from "@metamask/base-controller"; + import type { NetworkClientId, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller"; + import type { PreferencesControllerGetStateAction } from "@metamask/preferences-controller"; ++import type { AssetsContractController, StakedBalance } from "./AssetsContractController.cjs"; + /** + * The name of the {@link AccountTrackerController}. + */ +@@ -10,10 +11,12 @@ declare const controllerName = "AccountTrackerController"; + * @type AccountInformation + * + * Account information object +- * @property balance - Hex string of an account balancec in wei ++ * @property balance - Hex string of an account balance in wei ++ * @property stakedBalance - Hex string of an account staked balance in wei + */ + export type AccountInformation = { + balance: string; ++ stakedBalance?: string; + }; + /** + * @type AccountTrackerControllerState +@@ -62,18 +65,15 @@ type AccountTrackerPollingInput = { + networkClientId: NetworkClientId; + }; + declare const AccountTrackerController_base: (abstract new (...args: any[]) => { +- readonly "__#787890@#intervalIds": Record<string, NodeJS.Timeout>; +- "__#787890@#intervalLength": number | undefined; ++ readonly "__#784968@#intervalIds": Record<string, NodeJS.Timeout>; ++ "__#784968@#intervalLength": number | undefined; + setIntervalLength(intervalLength: number): void; + getIntervalLength(): number | undefined; + _startPolling(input: AccountTrackerPollingInput): void; + _stopPollingByPollingTokenSetId(key: string): void; +- readonly "__#787882@#pollingTokenSets": Map<string, Set<string>>; +- "__#787882@#callbacks": Map<string, Set<(input: AccountTrackerPollingInput) => void>>; ++ readonly "__#784960@#pollingTokenSets": Map<string, Set<string>>; ++ "__#784960@#callbacks": Map<string, Set<(input: AccountTrackerPollingInput) => void>>; + _executePoll(input: AccountTrackerPollingInput): Promise<void>; +- /** +- * The action that can be performed to get the state of the {@link AccountTrackerController}. +- */ + startPolling(input: AccountTrackerPollingInput): string; + stopAllPolling(): void; + stopPollingByPollingToken(pollingToken: string): void; +@@ -91,11 +91,15 @@ export declare class AccountTrackerController extends AccountTrackerController_b + * @param options.interval - Polling interval used to fetch new account balances. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller messaging system. ++ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. ++ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. + */ +- constructor({ interval, state, messenger, }: { ++ constructor({ interval, state, messenger, getStakedBalanceForChain, includeStakedAssets, }: { + interval?: number; + state?: Partial<AccountTrackerControllerState>; + messenger: AccountTrackerControllerMessenger; ++ getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain']; ++ includeStakedAssets?: boolean; + }); + private syncAccounts; + /** +@@ -128,6 +132,7 @@ export declare class AccountTrackerController extends AccountTrackerController_b + */ + syncBalanceWithAddresses(addresses: string[], networkClientId?: NetworkClientId): Promise<Record<string, { + balance: string; ++ stakedBalance?: StakedBalance; + }>>; + } + export default AccountTrackerController; +diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts +index b25212c..20d566f 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts ++++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts +@@ -2,6 +2,7 @@ import type { AccountsControllerSelectedEvmAccountChangeEvent, AccountsControlle + import type { ControllerStateChangeEvent, ControllerGetStateAction, RestrictedControllerMessenger } from "@metamask/base-controller"; + import type { NetworkClientId, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller"; + import type { PreferencesControllerGetStateAction } from "@metamask/preferences-controller"; ++import type { AssetsContractController, StakedBalance } from "./AssetsContractController.mjs"; + /** + * The name of the {@link AccountTrackerController}. + */ +@@ -10,10 +11,12 @@ declare const controllerName = "AccountTrackerController"; + * @type AccountInformation + * + * Account information object +- * @property balance - Hex string of an account balancec in wei ++ * @property balance - Hex string of an account balance in wei ++ * @property stakedBalance - Hex string of an account staked balance in wei + */ + export type AccountInformation = { + balance: string; ++ stakedBalance?: string; + }; + /** + * @type AccountTrackerControllerState +@@ -62,18 +65,15 @@ type AccountTrackerPollingInput = { + networkClientId: NetworkClientId; + }; + declare const AccountTrackerController_base: (abstract new (...args: any[]) => { +- readonly "__#787890@#intervalIds": Record<string, NodeJS.Timeout>; +- "__#787890@#intervalLength": number | undefined; ++ readonly "__#784968@#intervalIds": Record<string, NodeJS.Timeout>; ++ "__#784968@#intervalLength": number | undefined; + setIntervalLength(intervalLength: number): void; + getIntervalLength(): number | undefined; + _startPolling(input: AccountTrackerPollingInput): void; + _stopPollingByPollingTokenSetId(key: string): void; +- readonly "__#787882@#pollingTokenSets": Map<string, Set<string>>; +- "__#787882@#callbacks": Map<string, Set<(input: AccountTrackerPollingInput) => void>>; ++ readonly "__#784960@#pollingTokenSets": Map<string, Set<string>>; ++ "__#784960@#callbacks": Map<string, Set<(input: AccountTrackerPollingInput) => void>>; + _executePoll(input: AccountTrackerPollingInput): Promise<void>; +- /** +- * The action that can be performed to get the state of the {@link AccountTrackerController}. +- */ + startPolling(input: AccountTrackerPollingInput): string; + stopAllPolling(): void; + stopPollingByPollingToken(pollingToken: string): void; +@@ -91,11 +91,15 @@ export declare class AccountTrackerController extends AccountTrackerController_b + * @param options.interval - Polling interval used to fetch new account balances. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller messaging system. ++ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. ++ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. + */ +- constructor({ interval, state, messenger, }: { ++ constructor({ interval, state, messenger, getStakedBalanceForChain, includeStakedAssets, }: { + interval?: number; + state?: Partial<AccountTrackerControllerState>; + messenger: AccountTrackerControllerMessenger; ++ getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain']; ++ includeStakedAssets?: boolean; + }); + private syncAccounts; + /** +@@ -128,6 +132,7 @@ export declare class AccountTrackerController extends AccountTrackerController_b + */ + syncBalanceWithAddresses(addresses: string[], networkClientId?: NetworkClientId): Promise<Record<string, { + balance: string; ++ stakedBalance?: StakedBalance; + }>>; + } + export default AccountTrackerController; +diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs +index 4c4bf70..48cb006 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs ++++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs +@@ -1,15 +1,15 @@ +-var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { +- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); +- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); +- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +-}; + var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; + }; +-var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; ++var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; + function $importDefault(module) { + if (module?.__esModule) { + return module.default; +@@ -49,8 +49,10 @@ export class AccountTrackerController extends StaticIntervalPollingController() + * @param options.interval - Polling interval used to fetch new account balances. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller messaging system. ++ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. ++ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. + */ +- constructor({ interval = 10000, state, messenger, }) { ++ constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) { + const { selectedNetworkClientId } = messenger.call('NetworkController:getState'); + const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId); + super({ +@@ -67,7 +69,11 @@ export class AccountTrackerController extends StaticIntervalPollingController() + }); + _AccountTrackerController_instances.add(this); + _AccountTrackerController_refreshMutex.set(this, new Mutex()); ++ _AccountTrackerController_includeStakedAssets.set(this, void 0); ++ _AccountTrackerController_getStakedBalanceForChain.set(this, void 0); + _AccountTrackerController_handle.set(this, void 0); ++ __classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f"); ++ __classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f"); + this.setIntervalLength(interval); + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/no-floating-promises +@@ -173,6 +179,15 @@ export class AccountTrackerController extends StaticIntervalPollingController() + balance, + }; + } ++ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { ++ const stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); ++ if (stakedBalance) { ++ accountsForChain[address] = { ++ ...accountsForChain[address], ++ stakedBalance, ++ }; ++ } ++ } + } + this.update((state) => { + if (chainId === __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getCurrentChainId).call(this)) { +@@ -198,25 +213,30 @@ export class AccountTrackerController extends StaticIntervalPollingController() + return safelyExecuteWithTimeout(async () => { + assert(ethQuery, 'Provider not set.'); + const balance = await query(ethQuery, 'getBalance', [address]); +- return [address, balance]; ++ let stakedBalance; ++ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { ++ stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); ++ } ++ return [address, balance, stakedBalance]; + }); + })).then((value) => { + return value.reduce((obj, item) => { + if (!item) { + return obj; + } +- const [address, balance] = item; ++ const [address, balance, stakedBalance] = item; + return { + ...obj, + [address]: { + balance, ++ stakedBalance, + }, + }; + }, {}); + }); + } + } +-_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { ++_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { + const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState'); + const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); + return chainId; +diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs +index a8ba346..6b39491 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs +@@ -15,7 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { + }; + var _AssetsContractController_instances, _AssetsContractController_provider, _AssetsContractController_ipfsGateway, _AssetsContractController_chainId, _AssetsContractController_registerActionHandlers, _AssetsContractController_registerEventSubscriptions, _AssetsContractController_getCorrectProvider, _AssetsContractController_getCorrectChainId; + Object.defineProperty(exports, "__esModule", { value: true }); +-exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; ++exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; ++// import { BigNumber } from '@ethersproject/bignumber'; ++const bignumber_1 = require("@ethersproject/bignumber"); + const contracts_1 = require("@ethersproject/contracts"); + const providers_1 = require("@ethersproject/providers"); + const controller_utils_1 = require("@metamask/controller-utils"); +@@ -51,6 +53,10 @@ exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { + [assetsUtil_1.SupportedTokenDetectionNetworks.moonbeam]: '0x6aa75276052d96696134252587894ef5ffa520af', + [assetsUtil_1.SupportedTokenDetectionNetworks.moonriver]: '0x6aa75276052d96696134252587894ef5ffa520af', + }; ++exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID = { ++ [assetsUtil_1.SupportedStakedBalanceNetworks.mainnet]: '0x4fef9d741011476750a243ac70b9789a63dd47df', ++ [assetsUtil_1.SupportedStakedBalanceNetworks.holesky]: '0x37bf0883c27365cffcd0c4202918df930989891f', ++}; + exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; + /** + * The name of the {@link AssetsContractController} +@@ -333,6 +339,60 @@ class AssetsContractController { + } + return nonZeroBalances; + } ++ /** ++ * Get the staked ethereum balance for an address in a single call. ++ * ++ * @param address - The address to check staked ethereum balance for. ++ * @param networkClientId - Network Client ID to fetch the provider with. ++ * @returns The hex staked ethereum balance for address. ++ */ ++ async getStakedBalanceForChain(address, networkClientId) { ++ const chainId = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectChainId).call(this, networkClientId); ++ const provider = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectProvider).call(this, networkClientId); ++ // balance defaults to zero ++ let balance = bignumber_1.BigNumber.from(0); ++ // Only fetch staked balance on supported networks ++ if (![ ++ assetsUtil_1.SupportedStakedBalanceNetworks.mainnet, ++ assetsUtil_1.SupportedStakedBalanceNetworks.holesky, ++ ].includes(chainId)) { ++ return undefined; ++ } ++ // Only fetch staked balance if contract address exists ++ if (!((id) => id in exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID)(chainId)) { ++ return undefined; ++ } ++ const contractAddress = exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId]; ++ const abi = [ ++ { ++ inputs: [{ internalType: 'address', name: 'account', type: 'address' }], ++ name: 'getShares', ++ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], ++ stateMutability: 'view', ++ type: 'function', ++ }, ++ { ++ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], ++ name: 'convertToAssets', ++ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], ++ stateMutability: 'view', ++ type: 'function', ++ }, ++ ]; ++ try { ++ const contract = new contracts_1.Contract(contractAddress, abi, provider); ++ const userShares = await contract.getShares(address); ++ // convert shares to assets only if address shares > 0 else return default balance ++ if (!userShares.lte(0)) { ++ balance = await contract.convertToAssets(userShares.toString()); ++ } ++ } ++ catch (error) { ++ // if we get an error, log and return the default value ++ console.error(error); ++ } ++ return balance.toHexString(); ++ } + } + exports.AssetsContractController = AssetsContractController; + _AssetsContractController_provider = new WeakMap(), _AssetsContractController_ipfsGateway = new WeakMap(), _AssetsContractController_chainId = new WeakMap(), _AssetsContractController_instances = new WeakSet(), _AssetsContractController_registerActionHandlers = function _AssetsContractController_registerActionHandlers() { +diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts +index d7d9b61..b7bf69d 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts +@@ -32,6 +32,10 @@ export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: { + readonly "0x504": "0x6aa75276052d96696134252587894ef5ffa520af"; + readonly "0x505": "0x6aa75276052d96696134252587894ef5ffa520af"; + }; ++export declare const STAKING_CONTRACT_ADDRESS_BY_CHAINID: { ++ readonly "0x1": "0x4fef9d741011476750a243ac70b9789a63dd47df"; ++ readonly "0x4268": "0x37bf0883c27365cffcd0c4202918df930989891f"; ++}; + export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; + /** + * @type BalanceMap +@@ -98,6 +102,7 @@ export type AllowedEvents = PreferencesControllerStateChangeEvent | NetworkContr + * The messenger of the {@link AssetsContractController}. + */ + export type AssetsContractControllerMessenger = RestrictedControllerMessenger<typeof name, AssetsContractControllerActions | AllowedActions, AssetsContractControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>; ++export type StakedBalance = string | undefined; + /** + * Controller that interacts with contracts on mainnet through web3 + */ +@@ -272,6 +277,14 @@ export declare class AssetsContractController { + * @returns The list of non-zero token balances. + */ + getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[], networkClientId?: NetworkClientId): Promise<BalanceMap>; ++ /** ++ * Get the staked ethereum balance for an address in a single call. ++ * ++ * @param address - The address to check staked ethereum balance for. ++ * @param networkClientId - Network Client ID to fetch the provider with. ++ * @returns The hex staked ethereum balance for address. ++ */ ++ getStakedBalanceForChain(address: string, networkClientId?: NetworkClientId): Promise<StakedBalance>; + } + export default AssetsContractController; + //# sourceMappingURL=AssetsContractController.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts +index a7916fb..9cbee8c 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts ++++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts +@@ -32,6 +32,10 @@ export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: { + readonly "0x504": "0x6aa75276052d96696134252587894ef5ffa520af"; + readonly "0x505": "0x6aa75276052d96696134252587894ef5ffa520af"; + }; ++export declare const STAKING_CONTRACT_ADDRESS_BY_CHAINID: { ++ readonly "0x1": "0x4fef9d741011476750a243ac70b9789a63dd47df"; ++ readonly "0x4268": "0x37bf0883c27365cffcd0c4202918df930989891f"; ++}; + export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; + /** + * @type BalanceMap +@@ -98,6 +102,7 @@ export type AllowedEvents = PreferencesControllerStateChangeEvent | NetworkContr + * The messenger of the {@link AssetsContractController}. + */ + export type AssetsContractControllerMessenger = RestrictedControllerMessenger<typeof name, AssetsContractControllerActions | AllowedActions, AssetsContractControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>; ++export type StakedBalance = string | undefined; + /** + * Controller that interacts with contracts on mainnet through web3 + */ +@@ -272,6 +277,14 @@ export declare class AssetsContractController { + * @returns The list of non-zero token balances. + */ + getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[], networkClientId?: NetworkClientId): Promise<BalanceMap>; ++ /** ++ * Get the staked ethereum balance for an address in a single call. ++ * ++ * @param address - The address to check staked ethereum balance for. ++ * @param networkClientId - Network Client ID to fetch the provider with. ++ * @returns The hex staked ethereum balance for address. ++ */ ++ getStakedBalanceForChain(address: string, networkClientId?: NetworkClientId): Promise<StakedBalance>; + } + export default AssetsContractController; + //# sourceMappingURL=AssetsContractController.d.mts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs +index 15a0e56..deeb1eb 100644 +--- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs ++++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs +@@ -16,13 +16,15 @@ function $importDefault(module) { + } + return module; + } ++// import { BigNumber } from '@ethersproject/bignumber'; ++import { BigNumber } from "@ethersproject/bignumber"; + import { Contract } from "@ethersproject/contracts"; + import { Web3Provider } from "@ethersproject/providers"; + import { IPFS_DEFAULT_GATEWAY_URL } from "@metamask/controller-utils"; + import { getKnownPropertyNames } from "@metamask/utils"; + import $abiSingleCallBalancesContract from "single-call-balance-checker-abi"; + const abiSingleCallBalancesContract = $importDefault($abiSingleCallBalancesContract); +-import { SupportedTokenDetectionNetworks } from "./assetsUtil.mjs"; ++import { SupportedStakedBalanceNetworks, SupportedTokenDetectionNetworks } from "./assetsUtil.mjs"; + import { ERC20Standard } from "./Standards/ERC20Standard.mjs"; + import { ERC1155Standard } from "./Standards/NftStandards/ERC1155/ERC1155Standard.mjs"; + import { ERC721Standard } from "./Standards/NftStandards/ERC721/ERC721Standard.mjs"; +@@ -52,6 +54,10 @@ export const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { + [SupportedTokenDetectionNetworks.moonbeam]: '0x6aa75276052d96696134252587894ef5ffa520af', + [SupportedTokenDetectionNetworks.moonriver]: '0x6aa75276052d96696134252587894ef5ffa520af', + }; ++export const STAKING_CONTRACT_ADDRESS_BY_CHAINID = { ++ [SupportedStakedBalanceNetworks.mainnet]: '0x4fef9d741011476750a243ac70b9789a63dd47df', ++ [SupportedStakedBalanceNetworks.holesky]: '0x37bf0883c27365cffcd0c4202918df930989891f', ++}; + export const MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; + /** + * The name of the {@link AssetsContractController} +@@ -334,6 +340,60 @@ export class AssetsContractController { + } + return nonZeroBalances; + } ++ /** ++ * Get the staked ethereum balance for an address in a single call. ++ * ++ * @param address - The address to check staked ethereum balance for. ++ * @param networkClientId - Network Client ID to fetch the provider with. ++ * @returns The hex staked ethereum balance for address. ++ */ ++ async getStakedBalanceForChain(address, networkClientId) { ++ const chainId = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectChainId).call(this, networkClientId); ++ const provider = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectProvider).call(this, networkClientId); ++ // balance defaults to zero ++ let balance = BigNumber.from(0); ++ // Only fetch staked balance on supported networks ++ if (![ ++ SupportedStakedBalanceNetworks.mainnet, ++ SupportedStakedBalanceNetworks.holesky, ++ ].includes(chainId)) { ++ return undefined; ++ } ++ // Only fetch staked balance if contract address exists ++ if (!((id) => id in STAKING_CONTRACT_ADDRESS_BY_CHAINID)(chainId)) { ++ return undefined; ++ } ++ const contractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId]; ++ const abi = [ ++ { ++ inputs: [{ internalType: 'address', name: 'account', type: 'address' }], ++ name: 'getShares', ++ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], ++ stateMutability: 'view', ++ type: 'function', ++ }, ++ { ++ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], ++ name: 'convertToAssets', ++ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], ++ stateMutability: 'view', ++ type: 'function', ++ }, ++ ]; ++ try { ++ const contract = new Contract(contractAddress, abi, provider); ++ const userShares = await contract.getShares(address); ++ // convert shares to assets only if address shares > 0 else return default balance ++ if (!userShares.lte(0)) { ++ balance = await contract.convertToAssets(userShares.toString()); ++ } ++ } ++ catch (error) { ++ // if we get an error, log and return the default value ++ console.error(error); ++ } ++ return balance.toHexString(); ++ } + } + _AssetsContractController_provider = new WeakMap(), _AssetsContractController_ipfsGateway = new WeakMap(), _AssetsContractController_chainId = new WeakMap(), _AssetsContractController_instances = new WeakSet(), _AssetsContractController_registerActionHandlers = function _AssetsContractController_registerActionHandlers() { + const methodsExcludedFromMessenger = [ +diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.cjs b/node_modules/@metamask/assets-controllers/dist/NftController.cjs +index 82613cf..f7fd41e 100644 +--- a/node_modules/@metamask/assets-controllers/dist/NftController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/NftController.cjs +@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( + var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; + }; +-var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_openSeaEnabled, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; ++var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_displayNftMedia, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.NftController = exports.getDefaultNftControllerState = void 0; + const address_1 = require("@ethersproject/address"); +@@ -53,7 +53,7 @@ class NftController extends base_controller_1.BaseController { + * @param options - The controller options. + * @param options.chainId - The chain ID of the current network. + * @param options.ipfsGateway - The configured IPFS gateway. +- * @param options.openSeaEnabled - Controls whether the OpenSea API is used. ++ * @param options.displayNftMedia - Controls whether the OpenSea API is used. + * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. + * @param options.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. + * @param options.onNftAdded - Callback that is called when an NFT is added. Currently used pass data +@@ -61,7 +61,7 @@ class NftController extends base_controller_1.BaseController { + * @param options.messenger - The controller messenger. + * @param options.state - Initial state to set on this controller. + */ +- constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { ++ constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, displayNftMedia = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { + super({ + name: controllerName, + metadata: nftControllerMetadata, +@@ -76,14 +76,14 @@ class NftController extends base_controller_1.BaseController { + _NftController_selectedAccountId.set(this, void 0); + _NftController_chainId.set(this, void 0); + _NftController_ipfsGateway.set(this, void 0); +- _NftController_openSeaEnabled.set(this, void 0); ++ _NftController_displayNftMedia.set(this, void 0); + _NftController_useIpfsSubdomains.set(this, void 0); + _NftController_isIpfsGatewayEnabled.set(this, void 0); + _NftController_onNftAdded.set(this, void 0); + __classPrivateFieldSet(this, _NftController_selectedAccountId, this.messagingSystem.call('AccountsController:getSelectedAccount').id, "f"); + __classPrivateFieldSet(this, _NftController_chainId, initialChainId, "f"); + __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); +- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); ++ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); + __classPrivateFieldSet(this, _NftController_useIpfsSubdomains, useIpfsSubdomains, "f"); + __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); + __classPrivateFieldSet(this, _NftController_onNftAdded, onNftAdded, "f"); +@@ -97,6 +97,19 @@ class NftController extends base_controller_1.BaseController { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_onSelectedAccountChange).bind(this)); + } ++ ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset() { ++ this.update((state) => { ++ state.allNftContracts = {}; ++ state.allNfts = {}; ++ state.ignoredNfts = []; ++ }); ++ } ++ + getNftApi() { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions +@@ -581,7 +594,7 @@ class NftController extends base_controller_1.BaseController { + } + } + exports.NftController = NftController; +-_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_openSeaEnabled = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { ++_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_displayNftMedia = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { + const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); + __classPrivateFieldSet(this, _NftController_chainId, chainId, "f"); + }, _NftController_onPreferencesControllerStateChange = +@@ -589,16 +602,16 @@ _NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new Wea + * Handles the state change of the preference controller. + * @param preferencesState - The new state of the preference controller. + * @param preferencesState.ipfsGateway - The configured IPFS gateway. +- * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used. ++ * @param preferencesState.displayNftMedia - Controls whether the OpenSea API is used. + * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. + */ +-async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, openSeaEnabled, isIpfsGatewayEnabled, }) { ++async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, displayNftMedia, isIpfsGatewayEnabled, }) { + const selectedAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); + __classPrivateFieldSet(this, _NftController_selectedAccountId, selectedAccount.id, "f"); + __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); +- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); ++ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); + __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); +- const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || openSeaEnabled; ++ const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || displayNftMedia; + if (needsUpdateNftMetadata && selectedAccount) { + await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, selectedAccount); + } +@@ -611,7 +624,7 @@ async function _NftController_onSelectedAccountChange(internalAccount) { + const oldSelectedAccountId = __classPrivateFieldGet(this, _NftController_selectedAccountId, "f"); + __classPrivateFieldSet(this, _NftController_selectedAccountId, internalAccount.id, "f"); + const needsUpdateNftMetadata = ((__classPrivateFieldGet(this, _NftController_isIpfsGatewayEnabled, "f") && __classPrivateFieldGet(this, _NftController_ipfsGateway, "f") !== '') || +- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f")) && ++ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f")) && + oldSelectedAccountId !== internalAccount.id; + if (needsUpdateNftMetadata) { + await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, internalAccount); +@@ -686,6 +699,7 @@ async function _NftController_getNftInformationFromApi(contractAddress, tokenId) + description: null, + image: null, + standard: null, ++ error: 'Opensea import error', + }; + } + // if we've reached this point, we have successfully fetched some data for nftInformation +@@ -730,7 +744,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok + tokenURI: tokenURI ?? null, + }; + } +- const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f"); ++ const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_displayNftMedia, "f"); + if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { + return { + image: null, +@@ -739,6 +753,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok + standard: standard || null, + favorite: false, + tokenURI: tokenURI ?? null, ++ error: 'URI import error', + }; + } + if (hasIpfsTokenURI) { +@@ -777,6 +792,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok + standard: standard || null, + favorite: false, + tokenURI: tokenURI ?? null, ++ error: 'URI import error', + }; + } + }, _NftController_getNftURIAndStandard = +@@ -832,10 +848,21 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ + }); + const [blockchainMetadata, nftApiMetadata] = await Promise.all([ + (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromTokenURI).call(this, contractAddress, tokenId, networkClientId)), +- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f") && chainId === '0x1' ++ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f") && chainId === '0x1' + ? (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromApi).call(this, contractAddress, tokenId)) + : undefined, + ]); ++ if (blockchainMetadata?.error && nftApiMetadata?.error) { ++ return { ++ image: null, ++ name: null, ++ description: null, ++ standard: blockchainMetadata.standard ?? null, ++ favorite: false, ++ tokenURI: blockchainMetadata.tokenURI ?? null, ++ error: 'Both import failed', ++ }; ++ } + return { + ...nftApiMetadata, + name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null, +@@ -977,6 +1004,7 @@ async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadat + tokenId: tokenId.toString(), + standard: nftMetadata.standard, + source, ++ tokenURI: nftMetadata.tokenURI, + }); + } + } +diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts +index 6306bb6..7e9bfec 100644 +--- a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts +@@ -108,6 +108,7 @@ export type NftMetadata = { + creator?: string; + transactionId?: string; + tokenURI?: string | null; ++ error?: string; + collection?: Collection; + address?: string; + attributes?: Attributes[]; +@@ -419,6 +420,11 @@ export declare class NftController extends BaseController<typeof controllerName, + collections: Collection[]; + }>; + _requestApproval(suggestedNftMeta: SuggestedNftMeta): Promise<unknown>; ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset(): void; + } + export default NftController; + //# sourceMappingURL=NftController.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs +index f7b75d7..57ec79e 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs +@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); + }; +-var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled; ++var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled, _TokenBalancesController_updateInProgress; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.TokenBalancesController = exports.getDefaultTokenBalancesState = void 0; + const base_controller_1 = require("@metamask/base-controller"); +@@ -60,9 +60,11 @@ class TokenBalancesController extends base_controller_1.BaseController { + _TokenBalancesController_interval.set(this, void 0); + _TokenBalancesController_tokens.set(this, void 0); + _TokenBalancesController_disabled.set(this, void 0); ++ _TokenBalancesController_updateInProgress.set(this, void 0); + __classPrivateFieldSet(this, _TokenBalancesController_disabled, disabled, "f"); + __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); + __classPrivateFieldSet(this, _TokenBalancesController_tokens, tokens, "f"); ++ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, false, "f"); + this.messagingSystem.subscribe('TokensController:stateChange', ({ tokens: newTokens, detectedTokens }) => { + __classPrivateFieldSet(this, _TokenBalancesController_tokens, [...newTokens, ...detectedTokens], "f"); + // TODO: Either fix this lint violation or explain why it's necessary to ignore. +@@ -73,6 +75,18 @@ class TokenBalancesController extends base_controller_1.BaseController { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.poll(); + } ++ ++ ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset() { ++ this.update((state) => { ++ state.contractBalances = {}; ++ }); ++ } ++ + /** + * Allows controller to update tracked tokens contract balances. + */ +@@ -108,29 +122,37 @@ class TokenBalancesController extends base_controller_1.BaseController { + * Updates balances for all tokens. + */ + async updateBalances() { +- if (__classPrivateFieldGet(this, _TokenBalancesController_disabled, "f")) { ++ if (__classPrivateFieldGet(this, _TokenBalancesController_disabled, "f") || __classPrivateFieldGet(this, _TokenBalancesController_updateInProgress, "f")) { + return; + } ++ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, true, "f"); + const selectedInternalAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); + const newContractBalances = {}; +- for (const token of __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f")) { ++ const balancePromises = __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f").map((token) => { + const { address } = token; +- try { +- const balance = await this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address); ++ return this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address) ++ .then((balance) => { + newContractBalances[address] = (0, controller_utils_1.toHex)(balance); +- token.hasBalanceError = false; +- } +- catch (error) { ++ token = { ++ ...token, ++ hasBalanceError: false ++ } ++ }).catch((error) => { + newContractBalances[address] = (0, controller_utils_1.toHex)(0); +- token.hasBalanceError = true; +- } +- } ++ token = { ++ ...token, ++ hasBalanceError: true ++ } ++ }) ++ }); ++ await Promise.all(balancePromises); + this.update((state) => { + state.contractBalances = newContractBalances; + }); ++ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, false, "f"); + } + } + exports.TokenBalancesController = TokenBalancesController; +-_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(); ++_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(), _TokenBalancesController_updateInProgress = new WeakMap(); + exports.default = TokenBalancesController; + //# sourceMappingURL=TokenBalancesController.cjs.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts +index d252cab..ccc3d82 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts +@@ -77,6 +77,11 @@ export declare class TokenBalancesController extends BaseController<typeof contr + * Updates balances for all tokens. + */ + updateBalances(): Promise<void>; ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset(): void; + } + export default TokenBalancesController; + //# sourceMappingURL=TokenBalancesController.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs +index beb4f95..6aab19a 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs +@@ -117,6 +117,16 @@ class TokenRatesController extends (0, polling_controller_1.StaticIntervalPollin + __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_subscribeToTokensStateChange).call(this); + __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_subscribeToNetworkStateChange).call(this); + } ++ ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset() { ++ this.update((state) => { ++ state.marketData = {}; ++ }); ++ } + /** + * Allows controller to make active and passive polling requests + */ +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts +index a5d6404..0d3d017 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts +@@ -183,6 +183,11 @@ export declare class TokenRatesController extends TokenRatesController_base<type + * @param input.networkClientId - The network client ID used to get a ticker value. + */ + _executePoll({ networkClientId, }: TokenRatesPollingInput): Promise<void>; ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset(): void; + } + export default TokenRatesController; + //# sourceMappingURL=TokenRatesController.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs +index 742de5b..651d1dd 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs +@@ -109,6 +109,19 @@ class TokensController extends base_controller_1.BaseController { + } + }); + } ++ ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset() { ++ this.update((state) => { ++ state.allTokens = {}; ++ state.allIgnoredTokens = {}; ++ state.ignoredTokens = []; ++ state.tokens = []; ++ }); ++ } + /** + * Adds a token to the stored token list. + * +diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts +index 3fbbc66..4f99591 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts +@@ -160,6 +160,11 @@ export declare class TokensController extends BaseController<typeof controllerNa + * Removes all tokens from the ignored list. + */ + clearIgnoredTokens(): void; ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset(): void; + } + export default TokensController; + //# sourceMappingURL=TokensController.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/assetsUtil.cjs b/node_modules/@metamask/assets-controllers/dist/assetsUtil.cjs +index e90a1b6..c2e83cf 100644 +--- a/node_modules/@metamask/assets-controllers/dist/assetsUtil.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/assetsUtil.cjs +@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; + }; + Object.defineProperty(exports, "__esModule", { value: true }); +-exports.fetchTokenContractExchangeRates = exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.ethersBigNumberToBN = exports.addUrlProtocolPrefix = exports.getFormattedIpfsUrl = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.isTokenListSupportedForNetwork = exports.isTokenDetectionSupportedForNetwork = exports.SupportedTokenDetectionNetworks = exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.hasNewCollectionFields = exports.compareNftMetadata = exports.TOKEN_PRICES_BATCH_SIZE = void 0; ++exports.fetchTokenContractExchangeRates = exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.ethersBigNumberToBN = exports.addUrlProtocolPrefix = exports.getFormattedIpfsUrl = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.isTokenListSupportedForNetwork = exports.isTokenDetectionSupportedForNetwork = exports.SupportedStakedBalanceNetworks = exports.SupportedTokenDetectionNetworks = exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.hasNewCollectionFields = exports.compareNftMetadata = exports.TOKEN_PRICES_BATCH_SIZE = void 0; + const controller_utils_1 = require("@metamask/controller-utils"); + const utils_1 = require("@metamask/utils"); + const bn_js_1 = __importDefault(require("bn.js")); +@@ -168,6 +168,18 @@ var SupportedTokenDetectionNetworks; + // eslint-disable-next-line @typescript-eslint/naming-convention + SupportedTokenDetectionNetworks["moonriver"] = "0x505"; + })(SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = SupportedTokenDetectionNetworks = {})); ++/** ++ * Networks where staked balance is supported - Values are in hex format ++ */ ++var SupportedStakedBalanceNetworks; ++(function (SupportedStakedBalanceNetworks) { ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/naming-convention ++ SupportedStakedBalanceNetworks["mainnet"] = "0x1"; ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/naming-convention ++ SupportedStakedBalanceNetworks["holesky"] = "0x4268"; ++})(SupportedStakedBalanceNetworks || (exports.SupportedStakedBalanceNetworks = SupportedStakedBalanceNetworks = {})); + /** + * Check if token detection is enabled for certain networks. + * +diff --git a/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.cts b/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.cts +index 4007a3b..cca8a6b 100644 +--- a/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.cts +@@ -68,6 +68,13 @@ export declare enum SupportedTokenDetectionNetworks { + moonbeam = "0x504", + moonriver = "0x505" + } ++/** ++ * Networks where staked balance is supported - Values are in hex format ++ */ ++export declare enum SupportedStakedBalanceNetworks { ++ mainnet = "0x1", ++ holesky = "0x4268" ++} + /** + * Check if token detection is enabled for certain networks. + * +diff --git a/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.mts b/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.mts +index e4d7d0a..0674deb 100644 +--- a/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.mts ++++ b/node_modules/@metamask/assets-controllers/dist/assetsUtil.d.mts +@@ -68,6 +68,13 @@ export declare enum SupportedTokenDetectionNetworks { + moonbeam = "0x504", + moonriver = "0x505" + } ++/** ++ * Networks where staked balance is supported - Values are in hex format ++ */ ++export declare enum SupportedStakedBalanceNetworks { ++ mainnet = "0x1", ++ holesky = "0x4268" ++} + /** + * Check if token detection is enabled for certain networks. + * +diff --git a/node_modules/@metamask/assets-controllers/dist/assetsUtil.mjs b/node_modules/@metamask/assets-controllers/dist/assetsUtil.mjs +index a53a5f0..1ae8b32 100644 +--- a/node_modules/@metamask/assets-controllers/dist/assetsUtil.mjs ++++ b/node_modules/@metamask/assets-controllers/dist/assetsUtil.mjs +@@ -165,6 +165,18 @@ export var SupportedTokenDetectionNetworks; + // eslint-disable-next-line @typescript-eslint/naming-convention + SupportedTokenDetectionNetworks["moonriver"] = "0x505"; + })(SupportedTokenDetectionNetworks || (SupportedTokenDetectionNetworks = {})); ++/** ++ * Networks where staked balance is supported - Values are in hex format ++ */ ++export var SupportedStakedBalanceNetworks; ++(function (SupportedStakedBalanceNetworks) { ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/naming-convention ++ SupportedStakedBalanceNetworks["mainnet"] = "0x1"; ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/naming-convention ++ SupportedStakedBalanceNetworks["holesky"] = "0x4268"; ++})(SupportedStakedBalanceNetworks || (SupportedStakedBalanceNetworks = {})); + /** + * Check if token detection is enabled for certain networks. + * diff --git a/patches/@metamask+assets-controllers+43.1.1.patch b/patches/@metamask+assets-controllers+43.1.1.patch deleted file mode 100644 index f7e2d65047ef..000000000000 --- a/patches/@metamask+assets-controllers+43.1.1.patch +++ /dev/null @@ -1,297 +0,0 @@ -diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.cjs b/node_modules/@metamask/assets-controllers/dist/NftController.cjs -index 6ccbe9c..49270d6 100644 ---- a/node_modules/@metamask/assets-controllers/dist/NftController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/NftController.cjs -@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( - var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; - }; --var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_openSeaEnabled, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; -+var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_displayNftMedia, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.NftController = exports.getDefaultNftControllerState = void 0; - const address_1 = require("@ethersproject/address"); -@@ -53,7 +53,7 @@ class NftController extends base_controller_1.BaseController { - * @param options - The controller options. - * @param options.chainId - The chain ID of the current network. - * @param options.ipfsGateway - The configured IPFS gateway. -- * @param options.openSeaEnabled - Controls whether the OpenSea API is used. -+ * @param options.displayNftMedia - Controls whether the OpenSea API is used. - * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. - * @param options.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. - * @param options.onNftAdded - Callback that is called when an NFT is added. Currently used pass data -@@ -61,7 +61,7 @@ class NftController extends base_controller_1.BaseController { - * @param options.messenger - The controller messenger. - * @param options.state - Initial state to set on this controller. - */ -- constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { -+ constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, displayNftMedia = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { - super({ - name: controllerName, - metadata: nftControllerMetadata, -@@ -76,14 +76,14 @@ class NftController extends base_controller_1.BaseController { - _NftController_selectedAccountId.set(this, void 0); - _NftController_chainId.set(this, void 0); - _NftController_ipfsGateway.set(this, void 0); -- _NftController_openSeaEnabled.set(this, void 0); -+ _NftController_displayNftMedia.set(this, void 0); - _NftController_useIpfsSubdomains.set(this, void 0); - _NftController_isIpfsGatewayEnabled.set(this, void 0); - _NftController_onNftAdded.set(this, void 0); - __classPrivateFieldSet(this, _NftController_selectedAccountId, this.messagingSystem.call('AccountsController:getSelectedAccount').id, "f"); - __classPrivateFieldSet(this, _NftController_chainId, initialChainId, "f"); - __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); -- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); -+ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); - __classPrivateFieldSet(this, _NftController_useIpfsSubdomains, useIpfsSubdomains, "f"); - __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); - __classPrivateFieldSet(this, _NftController_onNftAdded, onNftAdded, "f"); -@@ -589,7 +589,7 @@ class NftController extends base_controller_1.BaseController { - } - } - exports.NftController = NftController; --_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_openSeaEnabled = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { -+_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_displayNftMedia = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { - const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - __classPrivateFieldSet(this, _NftController_chainId, chainId, "f"); - }, _NftController_onPreferencesControllerStateChange = -@@ -597,16 +597,16 @@ _NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new Wea - * Handles the state change of the preference controller. - * @param preferencesState - The new state of the preference controller. - * @param preferencesState.ipfsGateway - The configured IPFS gateway. -- * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used. -+ * @param preferencesState.displayNftMedia - Controls whether the OpenSea API is used. - * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. - */ --async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, openSeaEnabled, isIpfsGatewayEnabled, }) { -+async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, displayNftMedia, isIpfsGatewayEnabled, }) { - const selectedAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); - __classPrivateFieldSet(this, _NftController_selectedAccountId, selectedAccount.id, "f"); - __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); -- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); -+ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); - __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); -- const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || openSeaEnabled; -+ const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || displayNftMedia; - if (needsUpdateNftMetadata && selectedAccount) { - await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, selectedAccount); - } -@@ -619,7 +619,7 @@ async function _NftController_onSelectedAccountChange(internalAccount) { - const oldSelectedAccountId = __classPrivateFieldGet(this, _NftController_selectedAccountId, "f"); - __classPrivateFieldSet(this, _NftController_selectedAccountId, internalAccount.id, "f"); - const needsUpdateNftMetadata = ((__classPrivateFieldGet(this, _NftController_isIpfsGatewayEnabled, "f") && __classPrivateFieldGet(this, _NftController_ipfsGateway, "f") !== '') || -- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f")) && -+ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f")) && - oldSelectedAccountId !== internalAccount.id; - if (needsUpdateNftMetadata) { - await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, internalAccount); -@@ -694,6 +694,7 @@ async function _NftController_getNftInformationFromApi(contractAddress, tokenId) - description: null, - image: null, - standard: null, -+ error: 'Opensea import error', - }; - } - // if we've reached this point, we have successfully fetched some data for nftInformation -@@ -738,7 +739,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - tokenURI: tokenURI ?? null, - }; - } -- const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f"); -+ const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_displayNftMedia, "f"); - if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { - return { - image: null, -@@ -747,6 +748,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - standard: standard || null, - favorite: false, - tokenURI: tokenURI ?? null, -+ error: 'URI import error', - }; - } - if (hasIpfsTokenURI) { -@@ -785,6 +787,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - standard: standard || null, - favorite: false, - tokenURI: tokenURI ?? null, -+ error: 'URI import error', - }; - } - }, _NftController_getNftURIAndStandard = -@@ -840,10 +843,21 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ - }); - const [blockchainMetadata, nftApiMetadata] = await Promise.all([ - (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromTokenURI).call(this, contractAddress, tokenId, networkClientId)), -- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f") && chainId === '0x1' -+ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f") && chainId === '0x1' - ? (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromApi).call(this, contractAddress, tokenId)) - : undefined, - ]); -+ if (blockchainMetadata?.error && nftApiMetadata?.error) { -+ return { -+ image: null, -+ name: null, -+ description: null, -+ standard: blockchainMetadata.standard ?? null, -+ favorite: false, -+ tokenURI: blockchainMetadata.tokenURI ?? null, -+ error: 'Both import failed', -+ }; -+ } - return { - ...nftApiMetadata, - name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null, -@@ -985,6 +999,7 @@ async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadat - tokenId: tokenId.toString(), - standard: nftMetadata.standard, - source, -+ tokenURI: nftMetadata.tokenURI, - }); - } - } -diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -index a34725f..21e9d20 100644 ---- a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -@@ -108,6 +108,7 @@ export type NftMetadata = { - creator?: string; - transactionId?: string; - tokenURI?: string | null; -+ error?: string; - collection?: Collection; - address?: string; - attributes?: Attributes[]; -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -index 6f48d64..f9dc513 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); - return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); - }; --var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled; -+var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled, _TokenBalancesController_updateInProgress; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.TokenBalancesController = exports.getDefaultTokenBalancesState = void 0; - const base_controller_1 = require("@metamask/base-controller"); -@@ -60,9 +60,11 @@ class TokenBalancesController extends base_controller_1.BaseController { - _TokenBalancesController_interval.set(this, void 0); - _TokenBalancesController_tokens.set(this, void 0); - _TokenBalancesController_disabled.set(this, void 0); -+ _TokenBalancesController_updateInProgress.set(this, void 0); - __classPrivateFieldSet(this, _TokenBalancesController_disabled, disabled, "f"); - __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); - __classPrivateFieldSet(this, _TokenBalancesController_tokens, tokens, "f"); -+ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, false, "f"); - this.messagingSystem.subscribe('TokensController:stateChange', ({ tokens: newTokens, detectedTokens }) => { - __classPrivateFieldSet(this, _TokenBalancesController_tokens, [...newTokens, ...detectedTokens], "f"); - // TODO: Either fix this lint violation or explain why it's necessary to ignore. -@@ -113,21 +115,28 @@ class TokenBalancesController extends base_controller_1.BaseController { - } - const selectedInternalAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); - const newContractBalances = {}; -- for (const token of __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f")) { -+ const balancePromises = __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f").map((token) => { - const { address } = token; -- try { -- const balance = await this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address); -+ return this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address) -+ .then((balance) => { - newContractBalances[address] = (0, controller_utils_1.toHex)(balance); -- token.hasBalanceError = false; -- } -- catch (error) { -+ token = { -+ ...token, -+ hasBalanceError: false -+ } -+ }).catch((error) => { - newContractBalances[address] = (0, controller_utils_1.toHex)(0); -- token.hasBalanceError = true; -- } -- } -+ token = { -+ ...token, -+ hasBalanceError: true -+ } -+ }) -+ }); -+ await Promise.all(balancePromises); - this.update((state) => { - state.contractBalances = newContractBalances; - }); -+ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, updateInProgress, "f"); - } - /** - * Reset the controller state to the default state. -@@ -139,6 +148,6 @@ class TokenBalancesController extends base_controller_1.BaseController { - } - } - exports.TokenBalancesController = TokenBalancesController; --_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(); -+_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(), _TokenBalancesController_updateInProgress = new WeakMap(); - exports.default = TokenBalancesController; - //# sourceMappingURL=TokenBalancesController.cjs.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -index ab23c95..8fd5efd 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -@@ -204,13 +204,10 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo - // Try detecting tokens via Account API first if conditions allow - if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) { - const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks); -- // If API succeeds and no chains are left for RPC detection, we can return early -- if (apiResult?.result === 'success' && -- chainsToDetectUsingRpc.length === 0) { -- return; -+ // If the account API call failed, have those chains fall back to RPC detection -+ if (apiResult?.result === 'failed') { -+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } -- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection -- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } - // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc - if (chainsToDetectUsingRpc.length > 0) { -@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre - const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f") - .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks) - .catch(() => null); -- if (!tokenBalancesByChain || -- Object.keys(tokenBalancesByChain).length === 0) { -+ if (tokenBalancesByChain === null) { - return { result: 'failed' }; - } - // Process each chain ID individually -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.mjs b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.mjs -index f75eb5c..ebc30bb 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.mjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.mjs -@@ -205,13 +205,10 @@ export class TokenDetectionController extends StaticIntervalPollingController() - // Try detecting tokens via Account API first if conditions allow - if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) { - const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks); -- // If API succeeds and no chains are left for RPC detection, we can return early -- if (apiResult?.result === 'success' && -- chainsToDetectUsingRpc.length === 0) { -- return; -+ // If the account API call failed, have those chains fall back to RPC detection -+ if (apiResult?.result === 'failed') { -+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } -- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection -- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } - // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc - if (chainsToDetectUsingRpc.length > 0) { -@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre - const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f") - .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks) - .catch(() => null); -- if (!tokenBalancesByChain || -- Object.keys(tokenBalancesByChain).length === 0) { -+ if (tokenBalancesByChain === null) { - return { result: 'failed' }; - } - // Process each chain ID individually diff --git a/patches/@metamask+preferences-controller+13.3.0.patch b/patches/@metamask+preferences-controller+13.1.0.patch similarity index 53% rename from patches/@metamask+preferences-controller+13.3.0.patch rename to patches/@metamask+preferences-controller+13.1.0.patch index 8db07685937f..95e65bb7e0f8 100644 --- a/patches/@metamask+preferences-controller+13.3.0.patch +++ b/patches/@metamask+preferences-controller+13.1.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs -index a36c32e..2cf3d16 100644 +index 97182f2..107ef23 100644 --- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs +++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs @@ -17,7 +17,7 @@ const metadata = { @@ -11,20 +11,19 @@ index a36c32e..2cf3d16 100644 securityAlertsEnabled: { persist: true, anonymous: true }, selectedAddress: { persist: true, anonymous: false }, showTestNetworks: { persist: true, anonymous: true }, -@@ -26,10 +26,11 @@ const metadata = { +@@ -26,6 +26,11 @@ const metadata = { useTokenDetection: { persist: true, anonymous: true }, smartTransactionsOptInStatus: { persist: true, anonymous: false }, useTransactionSimulations: { persist: true, anonymous: true }, -- useMultiRpcMigration: { persist: true, anonymous: true }, -+ showMultiRpcModal: { persist: true, anonymous: true }, - useSafeChainsListValidation: { persist: true, anonymous: true }, - tokenSortConfig: { persist: true, anonymous: true }, - privacyMode: { persist: true, anonymous: true }, ++ useSafeChainsListValidation: { persist: true, anonymous: true }, ++ showMultiRpcModal: { persist: false, anonymous: false }, ++ tokenSortConfig: { persist: true, anonymous: false }, + tokenNetworkFilter: { persist: true, anonymous: false }, ++ privacyMode: { persist: true, anonymous: true }, }; const name = 'PreferencesController'; /** -@@ -45,7 +46,7 @@ function getDefaultPreferencesState() { +@@ -41,7 +46,7 @@ function getDefaultPreferencesState() { isIpfsGatewayEnabled: true, isMultiAccountBalancesEnabled: true, lostIdentities: {}, @@ -33,27 +32,23 @@ index a36c32e..2cf3d16 100644 securityAlertsEnabled: false, selectedAddress: '', showIncomingTransactions: { -@@ -73,16 +74,17 @@ function getDefaultPreferencesState() { - showTestNetworks: false, - useNftDetection: false, +@@ -71,6 +76,15 @@ function getDefaultPreferencesState() { useTokenDetection: true, -- useMultiRpcMigration: true, -+ showMultiRpcModal: false, - smartTransactionsOptInStatus: true, + smartTransactionsOptInStatus: false, useTransactionSimulations: true, - useSafeChainsListValidation: true, - tokenSortConfig: { -- key: 'tokenFiatAmount', -+ key: 'tokenFiatBalance', - order: 'dsc', - sortCallback: 'stringNumeric', - }, - privacyMode: false, ++ useSafeChainsListValidation: true, ++ showMultiRpcModal: false, ++ tokenSortConfig: { ++ key: 'tokenFiatBalance', ++ order: 'dsc', ++ sortCallback: 'stringNumeric' ++ }, + tokenNetworkFilter: {}, ++ privacyMode: false, }; } exports.getDefaultPreferencesState = getDefaultPreferencesState; -@@ -221,22 +223,22 @@ class PreferencesController extends base_controller_1.BaseController { +@@ -209,22 +223,22 @@ class PreferencesController extends base_controller_1.BaseController { * @param useNftDetection - Boolean indicating user preference on NFT detection. */ setUseNftDetection(useNftDetection) { @@ -83,30 +78,44 @@ index a36c32e..2cf3d16 100644 state.useNftDetection = false; } }); -@@ -300,13 +302,13 @@ class PreferencesController extends base_controller_1.BaseController { - /** - * Toggle multi rpc migration modal. - * -- * @param useMultiRpcMigration - Boolean indicating if the multi rpc modal will be displayed or not. -+ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. - */ -- setUseMultiRpcMigration(useMultiRpcMigration) { -+ setShowMultiRpcModal(showMultiRpcModal) { - this.update((state) => { -- state.useMultiRpcMigration = useMultiRpcMigration; -- if (!useMultiRpcMigration) { -- state.useMultiRpcMigration = false; -+ state.showMultiRpcModal = showMultiRpcModal; -+ if (!showMultiRpcModal) { -+ state.showMultiRpcModal = false; - } - }); - } -@@ -360,6 +362,16 @@ class PreferencesController extends base_controller_1.BaseController { - state.privacyMode = privacyMode; +@@ -305,6 +319,59 @@ class PreferencesController extends base_controller_1.BaseController { + state.useTransactionSimulations = useTransactionSimulations; }); } + /** ++ * Toggle the use safe chains list validation. ++ * ++ * @param useSafeChainsListValidation - Boolean indicating user preference on using chainid.network third part to check safe networks. ++ */ ++ setUseSafeChainsListValidation(useSafeChainsListValidation) { ++ this.update((state) => { ++ state.useSafeChainsListValidation = useSafeChainsListValidation; ++ }); ++ } ++ /** ++ * Toggle multi rpc migration modal. ++ * ++ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. ++ */ ++ setShowMultiRpcModal(showMultiRpcModal) { ++ this.update((state) => { ++ state.showMultiRpcModal = showMultiRpcModal; ++ if (showMultiRpcModal) { ++ state.showMultiRpcModal = false; ++ } ++ }); ++ } ++ /** ++ * Set the token sort configuration setting. ++ * ++ * @param tokenSortConfig - Object describing token sort configuration. ++ */ ++ setTokenSortConfig(tokenSortConfig) { ++ this.update((state) => { ++ state.tokenSortConfig = tokenSortConfig; ++ }); ++ } ++ /** + * Set the token network filter configuration setting. + * + * @param tokenNetworkFilter - Object describing token sort configuration. @@ -115,15 +124,25 @@ index a36c32e..2cf3d16 100644 + this.update((state) => { + state.tokenNetworkFilter = tokenNetworkFilter; + }); ++ } ++ /** ++ * A setter for the user preferences to enable/disable privacy mode. ++ * ++ * @param privacyMode - true to enable privacy mode, false to disable it. ++ */ ++ setPrivacyMode(privacyMode) { ++ this.update((state) => { ++ state.privacyMode = privacyMode; ++ }); + } } exports.PreferencesController = PreferencesController; _PreferencesController_instances = new WeakSet(), _PreferencesController_syncIdentities = function _PreferencesController_syncIdentities(addresses) { diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts -index b587817..ad05486 100644 +index 04a9d6f..5885d5b 100644 --- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts +++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts -@@ -70,7 +70,7 @@ export type PreferencesState = { +@@ -65,7 +65,7 @@ export type PreferencesState = { /** * Controls whether the OpenSea API is used */ @@ -132,27 +151,55 @@ index b587817..ad05486 100644 /** * Controls whether "security alerts" are enabled */ -@@ -108,7 +108,7 @@ export type PreferencesState = { - /** - * Controls whether Multi rpc modal is displayed or not +@@ -100,6 +100,26 @@ export type PreferencesState = { + * Controls whether transaction simulations are enabled */ -- useMultiRpcMigration: boolean; + useTransactionSimulations: boolean; ++ /** ++ * Controls whether to use the safe chains list validation ++ */ ++ useSafeChainsListValidation: boolean; ++ /** ++ * Controls whether Multi rpc modal is displayed or not ++ */ + showMultiRpcModal: boolean; - /** - * Controls whether to use the safe chains list validation - */ -@@ -121,6 +121,10 @@ export type PreferencesState = { - * Controls whether balance and assets are hidden or not - */ - privacyMode: boolean; ++ /** ++ * Controls token sorting controls ++ */ ++ tokenSortConfig: Record<string, string>; + /** + * Controls token filtering controls + */ + tokenNetworkFilter: Record<string, string>; ++ /** ++ * Controls whether balance and assets are hidden or not ++ */ ++ privacyMode: boolean; }; declare const name = "PreferencesController"; export type PreferencesControllerGetStateAction = ControllerGetStateAction<typeof name, PreferencesState>; -@@ -202,11 +206,11 @@ export declare class PreferencesController extends BaseController<typeof name, P +@@ -120,7 +140,7 @@ export declare function getDefaultPreferencesState(): { + isIpfsGatewayEnabled: boolean; + isMultiAccountBalancesEnabled: boolean; + lostIdentities: {}; +- openSeaEnabled: boolean; ++ displayNftMedia: boolean; + securityAlertsEnabled: boolean; + selectedAddress: string; + showIncomingTransactions: { +@@ -150,6 +170,11 @@ export declare function getDefaultPreferencesState(): { + useTokenDetection: boolean; + smartTransactionsOptInStatus: boolean; + useTransactionSimulations: boolean; ++ useSafeChainsListValidation: boolean; ++ showMultiRpcModal: boolean; ++ tokenSortConfig: Record<string, string>; ++ tokenNetworkFilter: Record<string, boolean>; ++ privacyMode: boolean; + }; + /** + * Controller that stores shared settings and exposes convenience methods +@@ -218,11 +243,11 @@ export declare class PreferencesController extends BaseController<typeof name, P */ setUseNftDetection(useNftDetection: boolean): void; /** @@ -160,35 +207,47 @@ index b587817..ad05486 100644 + * Toggle the display nft media enabled setting. * - * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. -+ * @param displayNftMedia - Boolean indicating user preference on using web2 third parties. ++ * * @param displayNftMedia - Boolean indicating user preference on using web2 third parties. */ - setOpenSeaEnabled(openSeaEnabled: boolean): void; + setDisplayNftMedia(displayNftMedia: boolean): void; /** * Toggle the security alert enabled setting. * -@@ -241,9 +245,9 @@ export declare class PreferencesController extends BaseController<typeof name, P - /** - * Toggle multi rpc migration modal. - * -- * @param useMultiRpcMigration - Boolean indicating if the multi rpc modal will be displayed or not. -+ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. +@@ -266,6 +291,35 @@ export declare class PreferencesController extends BaseController<typeof name, P + * @param useTransactionSimulations - true to enable transaction simulations, false to disable it. */ -- setUseMultiRpcMigration(useMultiRpcMigration: boolean): void; + setUseTransactionSimulations(useTransactionSimulations: boolean): void; ++ /** ++ * Toggle the use safe chains list validation. ++ * @param useSafeChainsListValidation - Boolean indicating user preference on using chainid.network third part to check safe networks. ++ */ ++ setUseSafeChainsListValidation(useSafeChainsListValidation: boolean): void; ++ /** ++ * Toggle multi rpc migration modal. ++ * ++ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. ++ */ + setShowMultiRpcModal(showMultiRpcModal: boolean): void; - /** - * A setter for the user to opt into smart transactions - * -@@ -274,6 +278,12 @@ export declare class PreferencesController extends BaseController<typeof name, P - * @param privacyMode - true to enable privacy mode, false to disable it. - */ - setPrivacyMode(privacyMode: boolean): void; ++ /** ++ * Set the token sort configuration setting. ++ * ++ * @param tokenSortConfig - Object describing token sort configuration. ++ */ ++ setTokenSortConfig(tokenSortConfig: Record<string, string>): void; + /** + * Set the token sort configuration setting. + * + * @param tokenNetworkFilter - Object describing token sort configuration. + */ + setTokenNetworkFilter(tokenNetworkFilter: Record<string, boolean>): void; ++ /** ++ * A setter for the user preferences to enable/disable privacy mode. ++ * ++ * @param privacyMode - true to enable privacy mode, false to disable it. ++ */ ++ setPrivacyMode(privacyMode: boolean): void; } export default PreferencesController; //# sourceMappingURL=PreferencesController.d.cts.map +\ No newline at end of file diff --git a/patches/@metamask+swaps-controller+9.0.12.patch b/patches/@metamask+swaps-controller+9.0.12.patch new file mode 100644 index 000000000000..9531473c60d8 --- /dev/null +++ b/patches/@metamask+swaps-controller+9.0.12.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/@metamask/swaps-controller/dist/constants.js b/node_modules/@metamask/swaps-controller/dist/constants.js +index 4c98522..523f25a 100644 +--- a/node_modules/@metamask/swaps-controller/dist/constants.js ++++ b/node_modules/@metamask/swaps-controller/dist/constants.js +@@ -129,8 +129,8 @@ exports.BSC_SWAPS_TOKEN_OBJECT = { + decimals: 18, + }; + exports.POLYGON_SWAPS_TOKEN_OBJECT = { +- symbol: 'MATIC', +- name: 'Matic', ++ symbol: 'POL', ++ name: 'Polygon', + address: exports.NATIVE_SWAPS_TOKEN_ADDRESS, + decimals: 18, + }; diff --git a/wdio/screen-objects/Modals/TransactionProtectionModal.js b/wdio/screen-objects/Modals/TransactionProtectionModal.js new file mode 100644 index 000000000000..14b3dc467d8b --- /dev/null +++ b/wdio/screen-objects/Modals/TransactionProtectionModal.js @@ -0,0 +1,33 @@ +import { + TransactionProtectionModalSelectorText +} from '../../../e2e/selectors/Transactions/TransactionProtectionModal.selectors'; +import Gestures from '../../helpers/Gestures'; +import Selectors from '../../helpers/Selectors'; + +class TransactionProtectionModal { + get header() { + return Selectors.getXpathElementByText( + TransactionProtectionModalSelectorText.HEADER, + ); + } + + get enableButton() { + return Selectors.getXpathElementByText( + TransactionProtectionModalSelectorText.ENABLE_BUTTON, + ); + } + + async isVisible() { + await expect(this.header).toBeDisplayed(); + } + + async isNotVisible() { + await expect(this.header).not.toBeDisplayed(); + } + + async tapEnableButton() { + await Gestures.waitAndTap(this.enableButton); + } +} + +export default new TransactionProtectionModal(); diff --git a/wdio/step-definitions/common-steps.js b/wdio/step-definitions/common-steps.js index a4e3313d3b49..69da7223da4d 100644 --- a/wdio/step-definitions/common-steps.js +++ b/wdio/step-definitions/common-steps.js @@ -17,6 +17,7 @@ import WhatsNewModal from '../screen-objects/Modals/WhatsNewModal'; import Gestures from '../helpers/Gestures'; import OnboardingSucessScreen from '../screen-objects/OnboardingSucessScreen.js'; import ExperienceEnhancerModal from '../screen-objects/Modals/ExperienceEnhancerModal'; +import TransactionProtectionModal from '../screen-objects/Modals/TransactionProtectionModal'; import SettingsScreen from '../screen-objects/SettingsScreen'; Then(/^the Welcome screen is displayed$/, async () => { @@ -319,6 +320,16 @@ Given(/^I close all the onboarding modals$/, async () => { console.log('The onboarding modal is not visible'); } + try { + await TransactionProtectionModal.isVisible(); + await TransactionProtectionModal.tapEnableButton(); + await TransactionProtectionModal.isNotVisible(); + } catch { + /* eslint-disable no-console */ + + console.log('The whats new modal is not visible'); + } + try { // Handle Marketing consent modal diff --git a/yarn.lock b/yarn.lock index 8c946af6af78..00e929ad1612 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4411,10 +4411,10 @@ "@metamask/utils" "^9.1.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^43.1.1": - version "43.1.1" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-43.1.1.tgz#f9c8b97765f06653ca45271ead5b7acea2a169e4" - integrity sha512-dR5XmIGRN9nMW7gPtl83iwhFF5MQzOz+Jv1tN/e5v+uNHZ6f6aYLCvESEL9fDgGOeJn81g2P39RH/fT/QpcbRA== +"@metamask/assets-controllers@^41.0.0": + version "41.0.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-41.0.0.tgz#b6d5b0064015bbd45bf9a85370c5c4ffd486de1d" + integrity sha512-FwnkCxV8IZPN8aCb2xWj4lB2hwVwW4UKkU+0XnD236EAUcWsWYUMf9Z1NNBE2t/NlOKs9xXjfdJRNG3dPJ71dg== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/address" "^5.7.0" @@ -4424,7 +4424,7 @@ "@metamask/abi-utils" "^2.0.3" "@metamask/base-controller" "^7.0.2" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.4.3" + "@metamask/controller-utils" "^11.4.2" "@metamask/eth-query" "^4.0.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/polling-controller" "^12.0.1" @@ -4449,10 +4449,10 @@ "@metamask/utils" "^8.1.0" immer "^9.0.6" -"@metamask/base-controller@^5.0.0", "@metamask/base-controller@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-5.0.2.tgz#ab3584f67d9f2ff80958df21558e61650074e565" - integrity sha512-izOaXXnLz9OXbdika0ZvIDf24pgsWNPI02Lm0E4eMU61ICpV78bzQB7YyIbMtF6MWnItw1RnX9jN6zNEmp5pdA== +"@metamask/base-controller@^4.0.1", "@metamask/base-controller@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-4.1.1.tgz#9b411adf4822de7382fe69d07bb6b3fc3e738923" + integrity sha512-sJdsd/XlyOa0kRJ16qbM+xeQ8peV1yZcYumJmHCClPK09MkAlxq7EzsrahVZXUCFwcxtSucf244pbttnVqNthw== dependencies: "@metamask/utils" "^8.3.0" immer "^9.0.6" @@ -4507,22 +4507,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-10.0.0.tgz#08f6576c1533ab919b1b15b640146e9598f732ea" - integrity sha512-vO6lwIr3VSkkR/A9VCzxcpgLJhzgMvUvaAU9SF8ulXIhRIh3Eur4VDcXtcKNGNB8oTZcKbKJrsmAJCVfPZQ+zQ== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@metamask/eth-query" "^4.0.0" - "@metamask/ethjs-unit" "^0.3.0" - "@metamask/utils" "^8.3.0" - "@spruceid/siwe-parser" "2.1.0" - "@types/bn.js" "^5.1.5" - bn.js "^5.2.1" - eth-ens-namehash "^2.0.8" - fast-deep-equal "^3.1.3" - -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.2", "@metamask/controller-utils@^11.4.3": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.1", "@metamask/controller-utils@^11.4.2": version "11.4.3" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.4.3.tgz#5763f0bbee2f3770c1ba42dd4869786afef849bd" integrity sha512-shrVCHFwIbt8qVcKbxe/mp5tOxjz6905/7ZIAnwUJKHYv7iEqfjyO1ibPoOknrZCF2vbXtP21b435g3v9DBNTQ== @@ -4538,18 +4523,16 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" -"@metamask/controller-utils@^9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-9.1.0.tgz#436ff37d339df3f4b0f31458881c6f1b1002c945" - integrity sha512-17XQhyhR1bC7NjQHJF2KhxStVeoFW8liQ/Z526cI3uVcKOgYRxxDwBiRGs+xzv9XAm7f1W73W83wnb8fcBxlxg== +"@metamask/controller-utils@^8.0.1", "@metamask/controller-utils@^8.0.2", "@metamask/controller-utils@^8.0.4": + version "8.0.4" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-8.0.4.tgz#78a952301ff4b2a501b31865ab0de434c6ea3cd2" + integrity sha512-R0+Q5ROnXKTtxAmiCH4TYHkGfbZTT8qzLeycJQVeJHXhpeYlAPxjs8m5fy1jwW1tX4r0MDWShx9iNUmHZS41jw== dependencies: "@ethereumjs/util" "^8.1.0" "@metamask/eth-query" "^4.0.0" "@metamask/ethjs-unit" "^0.3.0" "@metamask/utils" "^8.3.0" - "@spruceid/siwe-parser" "2.1.0" - "@types/bn.js" "^5.1.5" - bn.js "^5.2.1" + "@spruceid/siwe-parser" "1.1.3" eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" @@ -4612,7 +4595,7 @@ async-mutex "^0.5.0" pify "^5.0.0" -"@metamask/eth-json-rpc-infura@^9.1.0": +"@metamask/eth-json-rpc-infura@^9.0.0", "@metamask/eth-json-rpc-infura@^9.1.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-infura/-/eth-json-rpc-infura-9.1.0.tgz#8e09588ed58f49058615cab7040dcbce4682a292" integrity sha512-47x7evivl5XUsTsRoF9t27guCXgmfsbQq+pjHHFf87WoISGsgua6wVr91b1iVCv8MzQqupJBewtnG8AzWpwEEQ== @@ -4638,7 +4621,7 @@ pify "^3.0.0" safe-stable-stringify "^2.3.2" -"@metamask/eth-json-rpc-middleware@^12.1.1": +"@metamask/eth-json-rpc-middleware@^12.1.0", "@metamask/eth-json-rpc-middleware@^12.1.1": version "12.1.1" resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-middleware/-/eth-json-rpc-middleware-12.1.1.tgz#5b6a19386f420211cb554c637f0927b76dc3167a" integrity sha512-6N5y5CIo3mjJlD3oUaCPsAR5KGkxzt2pL+nQaRKwZ0Z0HtXIu0dIKf4awtfzJDNNQGhlPG5Im+kG1oxkh0FkSQ== @@ -4679,7 +4662,7 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^5.0.1" -"@metamask/eth-json-rpc-provider@^2.1.0": +"@metamask/eth-json-rpc-provider@^2.1.0", "@metamask/eth-json-rpc-provider@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-2.3.2.tgz#39a3ec6cdf82b6f2ce764ebfd9ff78997a2aa608" integrity sha512-VaZx++3gfi85+j9zB5TvqSWLeZ6QpsNjIk56Nq6OMDp2U8iUEPgjdA8CybOtkyDb88EbpuOSzHZcdHEeIX3zPw== @@ -4840,6 +4823,14 @@ dependencies: promise-to-callback "^1.0.0" +"@metamask/ethjs-unit@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@metamask/ethjs-unit/-/ethjs-unit-0.2.1.tgz#168d93d11ff6381c35fb44915252dbb425ebe4dd" + integrity sha512-4wjjaXN/yIHeZDWuExoZ/fapL9vP6+mOZJTuqsresx35zf2Rh0IAM5hbr54bhHY+9TFejCms2vTiIWatK0TzYw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + "@metamask/ethjs-unit@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@metamask/ethjs-unit/-/ethjs-unit-0.3.0.tgz#d44d21d3b4ad443fb0cdd0362ea07c6f51e68ec4" @@ -4856,21 +4847,20 @@ is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -"@metamask/gas-fee-controller@^15.1.2": - version "15.1.2" - resolved "https://registry.yarnpkg.com/@metamask/gas-fee-controller/-/gas-fee-controller-15.1.2.tgz#cb8d85906efa1ff42a159ec023287ff31a63e45b" - integrity sha512-9knWAuhWvz0XKLvjImgpjbF1umCs6gNG70dc09VvWlp9BNowrniIEDRzo7bcDGvAectE2Vp0rxaohgqY8c5AOA== +"@metamask/gas-fee-controller@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@metamask/gas-fee-controller/-/gas-fee-controller-12.0.0.tgz#05f476e65b5b56bdceaabc9299dc5d86ec15456b" + integrity sha512-woK0EEhQCGOvl3urdCOJTSzOnbfgH07bflK2A2SUJYi4r+juU3iL0WZd3ifi/qc52wgckFY+MKroSfiiap1vSg== dependencies: - "@metamask/base-controller" "^5.0.2" - "@metamask/controller-utils" "^9.1.0" + "@metamask/base-controller" "^4.0.1" + "@metamask/controller-utils" "^8.0.1" "@metamask/eth-query" "^4.0.0" - "@metamask/ethjs-unit" "^0.3.0" - "@metamask/network-controller" "^18.1.0" - "@metamask/polling-controller" "^6.0.2" - "@metamask/utils" "^8.3.0" - "@types/bn.js" "^5.1.5" + "@metamask/ethjs-unit" "^0.2.1" + "@metamask/network-controller" "^17.1.0" + "@metamask/polling-controller" "^4.0.0" + "@metamask/utils" "^8.2.0" "@types/uuid" "^8.3.0" - bn.js "^5.2.1" + ethereumjs-util "^7.0.10" uuid "^8.3.2" "@metamask/gas-fee-controller@^18.0.0": @@ -4890,16 +4880,7 @@ bn.js "^5.2.1" uuid "^8.3.2" -"@metamask/json-rpc-engine@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.0.tgz#d2beb23ca43596bf2e4a72c54c1d4c24fce1c8a6" - integrity sha512-10GzJR3G+MM1uS9tLEOw67fc8/kstCSwVoSqaL3fxYaWfUrM6RJWAq1jnMdVrLgyItDguC0d8fsW1FTmF856rQ== - dependencies: - "@metamask/rpc-errors" "^7.0.0" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^9.1.0" - -"@metamask/json-rpc-engine@^7.0.0", "@metamask/json-rpc-engine@^7.1.1", "@metamask/json-rpc-engine@^7.3.2": +"@metamask/json-rpc-engine@^7.0.0", "@metamask/json-rpc-engine@^7.1.1", "@metamask/json-rpc-engine@^7.3.2", "@metamask/json-rpc-engine@^7.3.3": version "7.3.3" resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz#f2b30a2164558014bfcca45db10f5af291d989af" integrity sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg== @@ -5033,23 +5014,23 @@ resolved "https://registry.yarnpkg.com/@metamask/mobile-provider/-/mobile-provider-3.0.0.tgz#8a6a5a0874c8cbe4b468f63dfc57117d207f9595" integrity sha512-XwFJk0rd9lAZR5xS3VC7ypEhD7DvZR2gi2Ch6PHnODIqeS9Te3OdVKK5+jHI4his8v/zs6LWdFdlRtx5/jL96w== -"@metamask/network-controller@^18.1.0": - version "18.1.3" - resolved "https://registry.yarnpkg.com/@metamask/network-controller/-/network-controller-18.1.3.tgz#0aa7dbaf06c7ccf1f381a151d13850eb32d197b0" - integrity sha512-B79qGwhdNcmGtYOQWMZXKVSt88dowyP4Nf979QEX0opYe6Z4eZMZnGBezdl74cAcezEiDE1ro6X8UahB11IOTg== - dependencies: - "@metamask/base-controller" "^5.0.2" - "@metamask/controller-utils" "^10.0.0" - "@metamask/eth-block-tracker" "^9.0.2" - "@metamask/eth-json-rpc-infura" "^9.1.0" - "@metamask/eth-json-rpc-middleware" "^12.1.1" - "@metamask/eth-json-rpc-provider" "^3.0.2" +"@metamask/network-controller@^17.1.0": + version "17.2.1" + resolved "https://registry.yarnpkg.com/@metamask/network-controller/-/network-controller-17.2.1.tgz#55d846e9e6000f3264e9ac1cc9c5b1493bac335a" + integrity sha512-+cWbnqJyuLO+a3c9Rmdr10onNXv03J2Knl+IAR8QztAczqegtXB0pwTYlclU+j73Rsi9XWrUxS6fMGK1Vb3r9g== + dependencies: + "@metamask/base-controller" "^4.1.1" + "@metamask/controller-utils" "^8.0.4" + "@metamask/eth-json-rpc-infura" "^9.0.0" + "@metamask/eth-json-rpc-middleware" "^12.1.0" + "@metamask/eth-json-rpc-provider" "^2.3.2" "@metamask/eth-query" "^4.0.0" - "@metamask/json-rpc-engine" "^8.0.2" + "@metamask/json-rpc-engine" "^7.3.3" "@metamask/rpc-errors" "^6.2.1" "@metamask/swappable-obj-proxy" "^2.2.0" "@metamask/utils" "^8.3.0" - async-mutex "^0.5.0" + async-mutex "^0.2.6" + eth-block-tracker "^8.0.0" immer "^9.0.6" uuid "^8.3.2" @@ -5188,6 +5169,21 @@ "@metamask/safe-event-emitter" "^3.0.0" readable-stream "^3.6.2" +"@metamask/permission-controller@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-10.0.0.tgz#821280763cc37e9597fe7d207b5da00a881ad32a" + integrity sha512-gwoDmSsUnAFIzSeJ9FqUmoUYNofdhA0buMkH1AVXC5i/eOsEZGVUU6dYThrmUSzBK/wyNNIUGjaUSj4eMMtR6Q== + dependencies: + "@metamask/base-controller" "^6.0.0" + "@metamask/controller-utils" "^11.0.0" + "@metamask/json-rpc-engine" "^9.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + "@types/deep-freeze-strict" "^1.1.0" + deep-freeze-strict "^1.1.1" + immer "^9.0.6" + nanoid "^3.1.31" + "@metamask/permission-controller@^11.0.0": version "11.0.0" resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-11.0.0.tgz#b205b97cfa95921aa87ffb1d479c064e9baf8cc0" @@ -5229,15 +5225,15 @@ fast-json-stable-stringify "^2.1.0" uuid "^8.3.2" -"@metamask/polling-controller@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@metamask/polling-controller/-/polling-controller-6.0.2.tgz#dbe3c7d6610729db0749d37ce0eebf846894bd0e" - integrity sha512-q8LsV9JhV+heyO+6IIOUyaT6SqQKbyZOopRvhFNcZa37yATiJbv7TqVxy3qynVgyddguKdCQQtnxlL49Lc7Q2Q== +"@metamask/polling-controller@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@metamask/polling-controller/-/polling-controller-4.0.0.tgz#20ab8195cbf388ecc9c03f64234557ae8a528d37" + integrity sha512-BPSj/a8+RzXevuKpxS6xp9E/sHQ57knYBciNmHMSqej3MTMIe5G3Iyv9I5jkCQA2WSWYUksB7exTtkpmKoNZYA== dependencies: - "@metamask/base-controller" "^5.0.2" - "@metamask/controller-utils" "^9.1.0" - "@metamask/network-controller" "^18.1.0" - "@metamask/utils" "^8.3.0" + "@metamask/base-controller" "^4.0.1" + "@metamask/controller-utils" "^8.0.1" + "@metamask/network-controller" "^17.1.0" + "@metamask/utils" "^8.2.0" "@types/uuid" "^8.3.0" fast-json-stable-stringify "^2.1.0" uuid "^8.3.2" @@ -5277,13 +5273,13 @@ eslint-plugin-n "^16.6.2" json-rpc-random-id "^1.0.1" -"@metamask/preferences-controller@^13.3.0": - version "13.3.0" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-13.3.0.tgz#5c10001148dc335d8dcc3e3118154961b1a2f377" - integrity sha512-USPwqvCxk9C4GlpZRrd9hYhpZz2vpBl8S6TyJQeX0maY2m+vrbahhUukq/6zPWJEjkthqKe8Ndu5KKKCXv1Fyg== +"@metamask/preferences-controller@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-13.1.0.tgz#e407f6a0879d5fa4178caa0bdd4e310d178f0b34" + integrity sha512-fRURU9ZJaztJ+dPq+DrLbEcddhAoRDO5poagb+T8cFmIDxvTA1OKR1Bz8mgn91xMTuWMg95M+bPnb5dbRbRViw== dependencies: - "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.2" + "@metamask/base-controller" "^7.0.1" + "@metamask/controller-utils" "^11.3.0" "@metamask/profile-sync-controller@^0.9.7": version "0.9.7" @@ -5370,7 +5366,7 @@ escape-string-regexp "^4.0.0" invariant "2.2.4" -"@metamask/rpc-errors@7.0.1", "@metamask/rpc-errors@^6.0.0", "@metamask/rpc-errors@^6.2.1", "@metamask/rpc-errors@^6.3.1", "@metamask/rpc-errors@^7.0.0", "@metamask/rpc-errors@^7.0.1": +"@metamask/rpc-errors@7.0.1", "@metamask/rpc-errors@^6.0.0", "@metamask/rpc-errors@^6.2.1", "@metamask/rpc-errors@^6.3.1", "@metamask/rpc-errors@^7.0.1": version "7.0.1" resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-7.0.1.tgz#0eb2231a1d5e6bb102df5ac07f365c695bf70055" integrity sha512-EeQGYioq845w2iBmiR9LHYqHhYIaeDTmxprHpPE3BTlkLB74P0xLv/TivOn4snNLowiC5ekOXfcUzCQszTDmSg== @@ -5407,23 +5403,25 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/selected-network-controller@^18.0.2": - version "18.0.2" - resolved "https://registry.yarnpkg.com/@metamask/selected-network-controller/-/selected-network-controller-18.0.2.tgz#a6bd7916c47307999cada50d8e5d3d839a29246c" - integrity sha512-0a0uAW3EH56zWuDtCw5al6PcwWjDEsy4ydoUV77+Ko8h46WsS8gbV4VpoKAN+MJpihty0LVM8J6whT3Z5qP7iQ== +"@metamask/selected-network-controller@^15.0.2": + version "15.0.2" + resolved "https://registry.yarnpkg.com/@metamask/selected-network-controller/-/selected-network-controller-15.0.2.tgz#6373156a1e837839ea771b61aa065f69b47a90c9" + integrity sha512-B4jZO0g49NwK78Zh391UOZsGYlHlWw/GMVkb9rXDLBYpbXSgiVMb5mdgdfqSWvi2YnFuwiCDPTszwBTM+M+aLA== dependencies: - "@metamask/base-controller" "^7.0.1" - "@metamask/json-rpc-engine" "^10.0.0" + "@metamask/base-controller" "^6.0.0" + "@metamask/json-rpc-engine" "^9.0.0" + "@metamask/network-controller" "^19.0.0" + "@metamask/permission-controller" "^10.0.0" "@metamask/swappable-obj-proxy" "^2.2.0" - "@metamask/utils" "^9.1.0" + "@metamask/utils" "^8.3.0" "@metamask/signature-controller@^21.0.0": - version "21.1.0" - resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-21.1.0.tgz#04b164dc0dfaeee0bf77d1be50b5f9be39c8c334" - integrity sha512-WD99N98DUio/Ya21tQtRV8IXAvdoakXL0icFoBir8Dnv4ZuAwFkb5TbalhWfhyAA2rwED2amTbAmIrzcvXMagw== + version "21.0.0" + resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-21.0.0.tgz#f7e10f2aab440c1a059c1867b5fba9b8cbabaeab" + integrity sha512-hM21XgDo0LYtNVy+k24bLLDxhgOarpCabutUu1ckTJgbE51X4YGMjH/C5I+G8T16mTwsVBb0B6h4CdeUYPoNiQ== dependencies: "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.2" + "@metamask/controller-utils" "^11.4.1" "@metamask/eth-sig-util" "^8.0.0" "@metamask/utils" "^10.0.0" jsonschema "^1.2.4" @@ -5640,19 +5638,20 @@ resolved "https://registry.yarnpkg.com/@metamask/swappable-obj-proxy/-/swappable-obj-proxy-2.2.0.tgz#31b8e0ce57e28bf9847b3b24b214996f7748cc99" integrity sha512-0OjVwQtrrPFRGipw64yDUQA0CUXCK161LWCv2KlTTDZD8BKeWSNb0gbnpDI7HvhsJ0gki5gScZj1hF3ShDnBzA== -"@metamask/swaps-controller@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-10.0.0.tgz#4b6bd021bd071bec742f613542bb7f1a99d6c2ff" - integrity sha512-52sghQp1dxoAXo9LXfzHclHd7UheY3NXNqSLFNruxMxgzFEQKBKiqXoLxtQkNYfmV4AoP5bUpGeZ3wELVHG8Jw== +"@metamask/swaps-controller@^9.0.12": + version "9.0.12" + resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-9.0.12.tgz#de9c983522a137f5cb76ba93449bb719dcea587a" + integrity sha512-5BBzQVQM7CrrJkm9PqR6VELYaXmzQhLm9k09IE73TWp6GXrjhGXE7PxNQsd7r1Eu7hdAJBPeG+rAgE14bwYVlA== dependencies: "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" - "@metamask/base-controller" "^5.0.0" - "@metamask/controller-utils" "^10.0.0" + "@metamask/base-controller" "^4.1.1" + "@metamask/controller-utils" "^8.0.2" "@metamask/eth-query" "^4.0.0" - "@metamask/gas-fee-controller" "^15.1.2" + "@metamask/gas-fee-controller" "^12.0.0" "@metamask/utils" "^8.3.0" - async-mutex "^0.5.0" + abort-controller "^3.0.0" + async-mutex "^0.4.1" bignumber.js "^9.0.1" bn.js "^5.2.1" human-standard-token-abi "^2.0.0" @@ -5691,10 +5690,10 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/transaction-controller@^39.1.0": - version "39.1.0" - resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-39.1.0.tgz#148767a56610e194066ac4c5f2c584706d0c8b42" - integrity sha512-8+WWxsfORiUbOQTM/MkLJzF7Wk+lN2Df0eEGy8zoNfBBzbKykmajr/FqJV+WPUGnjsMr6pSvinYWGUgmjAhDng== +"@metamask/transaction-controller@^38.3.0": + version "38.3.0" + resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-38.3.0.tgz#51d4c5739da004b0e498b40273f838ee6f17be04" + integrity sha512-Ogj534hgT6ng6iTL0Wf3aHf17kZcY0F1xHFdGX9fXLLkPeTEIPisiYbZrZhzT4N00QJzBt+RMVLoeg42H3cozw== dependencies: "@ethereumjs/common" "^3.2.0" "@ethereumjs/tx" "^4.2.0" @@ -5703,7 +5702,7 @@ "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.3" + "@metamask/controller-utils" "^11.4.2" "@metamask/eth-query" "^4.0.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/nonce-tracker" "^6.0.0" @@ -7383,6 +7382,13 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@spruceid/siwe-parser@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-1.1.3.tgz#0eebe8bbd63c6de89cb44c06b6329b00b305df65" + integrity sha512-oQ8PcwDqjGWJvLmvAF2yzd6iniiWxK0Qtz+Dw+gLD/W5zOQJiKIUXwslHOm8VB8OOOKW9vfR3dnPBhHaZDvRsw== + dependencies: + apg-js "^4.1.1" + "@spruceid/siwe-parser@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-2.1.0.tgz#59859ccfd02403179bcf115d9e02a7dc953a0820" @@ -12931,6 +12937,13 @@ async-lock@^1.0.0, async-lock@^1.2.2: resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.2.tgz#56668613f91c1c55432b4db73e65c9ced664e789" integrity sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA== +async-mutex@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.6.tgz#0d7a3deb978bc2b984d5908a2038e1ae2e54ff40" + integrity sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw== + dependencies: + tslib "^2.0.0" + async-mutex@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.1.tgz#7033af665f1c7cebed8b878267a43ba9e77c5f67" @@ -12938,6 +12951,13 @@ async-mutex@^0.3.1: dependencies: tslib "^2.1.0" +async-mutex@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.1.tgz#bccf55b96f2baf8df90ed798cb5544a1f6ee4c2c" + integrity sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA== + dependencies: + tslib "^2.4.0" + async-mutex@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" @@ -17012,6 +17032,17 @@ eth-block-tracker@^7.0.1: json-rpc-random-id "^1.0.1" pify "^3.0.0" +eth-block-tracker@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-8.1.0.tgz#6ca4f6b955ff3e114f5aa0e8d36b11196ad1ea92" + integrity sha512-cdP9GMtJV87d4yuj4A3WX7gHIoJP4T4eeGgVW1jLjC/H7xuJsjs9vtwy9DJZvcd2cpRYZNQ7eWsdoJriHfi67Q== + dependencies: + "@metamask/eth-json-rpc-provider" "^2.1.0" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.1.0" + json-rpc-random-id "^1.0.1" + pify "^5.0.0" + eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" @@ -17128,7 +17159,7 @@ ethereumjs-util@^5.0.0: rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-util@^7.0.8, ethereumjs-util@^7.1.2: +ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.8, ethereumjs-util@^7.1.2: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==