From e620f038a5bdc6cf65ddad7ab4e59050d30e15ca Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Wed, 25 Jun 2025 16:16:46 -0400 Subject: [PATCH 01/55] feat: create account details component that switches into the correct account type\'s account details page --- .../account-details/account-details.test.tsx | 113 +++++++++++++++++ .../account-details/account-details.tsx | 86 +++++++++++++ .../account-type-utils.test.ts | 114 +++++++++++++++++ .../account-details/account-type-utils.ts | 120 ++++++++++++++++++ .../account-details/evm-account-details.tsx | 59 +++++++++ .../account-details/index.ts | 3 + 6 files changed, 495 insertions(+) create mode 100644 ui/pages/multichain-accounts/account-details/account-details.test.tsx create mode 100644 ui/pages/multichain-accounts/account-details/account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/account-type-utils.test.ts create mode 100644 ui/pages/multichain-accounts/account-details/account-type-utils.ts create mode 100644 ui/pages/multichain-accounts/account-details/evm-account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/index.ts diff --git a/ui/pages/multichain-accounts/account-details/account-details.test.tsx b/ui/pages/multichain-accounts/account-details/account-details.test.tsx new file mode 100644 index 00000000000..d19452cf5ae --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/account-details.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { renderWithProvider } from '../../../../test/jest'; +import { + MOCK_ACCOUNT_EOA, + MOCK_ACCOUNT_ERC4337, + MOCK_ACCOUNT_SOLANA_MAINNET, +} from '../../../../test/data/mock-accounts'; +import { AccountDetails } from './account-details'; + +const middleware = [thunk]; +const mockStore = configureMockStore(middleware); + +// Mock the useHistory hook +const mockPush = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockPush, + }), +})); + +// Mock the useI18nContext hook +jest.mock('../../../hooks/useI18nContext', () => ({ + useI18nContext: () => (key: string) => key, +})); + +const createMockState = (address: string, account = MOCK_ACCOUNT_EOA) => ({ + appState: { + accountDetailsAddress: address, + }, + metamask: { + internalAccounts: { + accounts: { + [account.id]: { + ...account, + address, + }, + }, + selectedAccount: account.id, + }, + }, +}); + +describe('AccountDetails', () => { + beforeEach(() => { + mockPush.mockClear(); + }); + + describe('Account Type Detection', () => { + it('should render EVM account details for EOA accounts', () => { + const state = createMockState(MOCK_ACCOUNT_EOA.address, MOCK_ACCOUNT_EOA); + const store = mockStore(state); + + renderWithProvider( + + + , + store, + ); + + // Should render the base account details (which includes account name) + expect(screen.getByText('Account 1')).toBeInTheDocument(); + }); + + it('should render EVM account details for ERC-4337 accounts', () => { + const state = createMockState(MOCK_ACCOUNT_ERC4337.address, MOCK_ACCOUNT_ERC4337); + const store = mockStore(state); + + renderWithProvider( + + + , + store, + ); + + // Should render the base account details (which includes account name) + expect(screen.getByText('Account 2')).toBeInTheDocument(); + }); + + it('should render base account details for Solana accounts', () => { + const state = createMockState(MOCK_ACCOUNT_SOLANA_MAINNET.address, MOCK_ACCOUNT_SOLANA_MAINNET); + const store = mockStore(state); + + renderWithProvider( + + + , + store, + ); + + // Should render the base account details (which includes account name) + expect(screen.getByText('Solana Account')).toBeInTheDocument(); + }); + + it('should navigate to default route when no address is provided', () => { + const state = createMockState('', MOCK_ACCOUNT_EOA); + const store = mockStore(state); + + renderWithProvider( + + + , + store, + ); + + expect(mockPush).toHaveBeenCalledWith('/'); + }); + }); +}); \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/account-details.tsx b/ui/pages/multichain-accounts/account-details/account-details.tsx new file mode 100644 index 00000000000..e896ba36983 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/account-details.tsx @@ -0,0 +1,86 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { AppSliceState } from '../../../ducks/app/app'; +import { getInternalAccountByAddress } from '../../../selectors'; +import { setAccountDetailsAddress } from '../../../store/actions'; +import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { EVMAccountDetails } from './evm-account-details'; +import { getAccountTypeCategory } from './account-type-utils'; + +// Import specific account type components (to be created) +// import { SolanaAccountDetails } from './solana-account-details'; +// import { HardwareAccountDetails } from './hardware-account-details'; +// import { PrivateKeyAccountDetails } from './private-key-account-details'; +// import { InstitutionalAccountDetails } from './institutional-account-details'; + +type AccountDetailsProps = { + children?: React.ReactNode | React.ReactNode[]; +}; + +export const AccountDetails = ({ children }: AccountDetailsProps) => { + const dispatch = useDispatch(); + const history = useHistory(); + const address = useSelector( + (state: AppSliceState) => state.appState.accountDetailsAddress, + ); + + const account = useSelector((state) => + getInternalAccountByAddress(state, address), + ); + + const handleNavigation = useCallback(() => { + dispatch(setAccountDetailsAddress('')); + history.push(DEFAULT_ROUTE); + }, [history, dispatch]); + + useEffect(() => { + if (!address) { + history.push(DEFAULT_ROUTE); + } + }, [dispatch, address, history]); + + if (!account) { + return null; + } + + const accountTypeCategory = getAccountTypeCategory(account); + + const renderAccountDetailsByType = () => { + switch (accountTypeCategory) { + case 'evm': + return {children}; + + case 'solana': + // TODO: Create SolanaAccountDetails component + return {children}; + + case 'hardware': + // TODO: Create HardwareAccountDetails component + return {children}; + + case 'private-key': + // TODO: Create PrivateKeyAccountDetails component + return {children}; + + case 'institutional-evm': + // TODO: Create InstitutionalAccountDetails component + return {children}; + + case 'bitcoin': + // TODO: Create BitcoinAccountDetails component + return {children}; + + case 'snap': + // TODO: Create SnapAccountDetails component + return {children}; + + default: + // Fallback to base account details for unknown types + return {children}; + } + }; + + return renderAccountDetailsByType(); +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts new file mode 100644 index 00000000000..07914e638b5 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts @@ -0,0 +1,114 @@ +import { + MOCK_ACCOUNT_EOA, + MOCK_ACCOUNT_ERC4337, + MOCK_ACCOUNT_SOLANA_MAINNET, +} from '../../../../test/data/mock-accounts'; +import { + getAccountTypeCategory, + isEVMAccount, + isSolanaAccount, + isHardwareAccount, + isPrivateKeyAccount, + isInstitutionalEVMAccount, + isBitcoinAccount, + isSnapAccount, +} from './account-type-utils'; + +describe('Account Type Utils', () => { + describe('getAccountTypeCategory', () => { + it('should return "evm" for EOA accounts', () => { + expect(getAccountTypeCategory(MOCK_ACCOUNT_EOA)).toBe('evm'); + }); + + it('should return "evm" for ERC-4337 accounts', () => { + expect(getAccountTypeCategory(MOCK_ACCOUNT_ERC4337)).toBe('evm'); + }); + + it('should return "solana" for Solana accounts', () => { + expect(getAccountTypeCategory(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe('solana'); + }); + + it('should return "unknown" for null/undefined accounts', () => { + expect(getAccountTypeCategory(null as any)).toBe('unknown'); + expect(getAccountTypeCategory(undefined as any)).toBe('unknown'); + }); + }); + + describe('isEVMAccount', () => { + it('should return true for EOA accounts', () => { + expect(isEVMAccount(MOCK_ACCOUNT_EOA)).toBe(true); + }); + + it('should return true for ERC-4337 accounts', () => { + expect(isEVMAccount(MOCK_ACCOUNT_ERC4337)).toBe(true); + }); + + it('should return false for Solana accounts', () => { + expect(isEVMAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); + }); + }); + + describe('isSolanaAccount', () => { + it('should return true for Solana accounts', () => { + expect(isSolanaAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(true); + }); + + it('should return false for EOA accounts', () => { + expect(isSolanaAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for ERC-4337 accounts', () => { + expect(isSolanaAccount(MOCK_ACCOUNT_ERC4337)).toBe(false); + }); + }); + + describe('isHardwareAccount', () => { + it('should return false for EOA accounts', () => { + expect(isHardwareAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for Solana accounts', () => { + expect(isHardwareAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); + }); + }); + + describe('isPrivateKeyAccount', () => { + it('should return false for EOA accounts', () => { + expect(isPrivateKeyAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for Solana accounts', () => { + expect(isPrivateKeyAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); + }); + }); + + describe('isInstitutionalEVMAccount', () => { + it('should return false for regular EOA accounts', () => { + expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for regular ERC-4337 accounts', () => { + expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_ERC4337)).toBe(false); + }); + }); + + describe('isBitcoinAccount', () => { + it('should return false for EOA accounts', () => { + expect(isBitcoinAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for Solana accounts', () => { + expect(isBitcoinAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); + }); + }); + + describe('isSnapAccount', () => { + it('should return false for EOA accounts', () => { + expect(isSnapAccount(MOCK_ACCOUNT_EOA)).toBe(false); + }); + + it('should return false for Solana accounts', () => { + expect(isSnapAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.ts new file mode 100644 index 00000000000..fe3f974ea60 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.ts @@ -0,0 +1,120 @@ +import { + EthAccountType, + BtcAccountType, + SolAccountType, + isEvmAccountType +} from '@metamask/keyring-api'; +import { KeyringTypes } from '@metamask/keyring-controller'; +import { InternalAccount } from '@metamask/keyring-internal-api'; + +export type AccountTypeCategory = + | 'evm' + | 'solana' + | 'hardware' + | 'private-key' + | 'institutional-evm' + | 'bitcoin' + | 'snap' + | 'unknown'; + +/** + * Determines the account type category based on the account's type and keyring information + */ +export const getAccountTypeCategory = (account: InternalAccount): AccountTypeCategory => { + if (!account) return 'unknown'; + + const { type, metadata } = account; + const keyringType = metadata?.keyring?.type; + const snapId = metadata?.snap?.id; + + // EVM accounts (EOA and ERC-4337) + if (isEvmAccountType(type)) { + // Check if it's an institutional account (through institutional snap) + if (snapId === 'npm:@metamask/institutional-wallet-snap') { + return 'institutional-evm'; + } + return 'evm'; + } + + // Hardware accounts + if (keyringType && [ + KeyringTypes.ledger, + KeyringTypes.trezor, + KeyringTypes.oneKey, + KeyringTypes.lattice, + KeyringTypes.qr + ].includes(keyringType)) { + return 'hardware'; + } + + // Private key accounts + if (keyringType === KeyringTypes.simple) { + return 'private-key'; + } + + // Solana accounts + if (type === SolAccountType.DataAccount) { + return 'solana'; + } + + // Bitcoin accounts + if (Object.values(BtcAccountType).includes(type)) { + return 'bitcoin'; + } + + // Snap accounts (non-institutional) + if (keyringType === KeyringTypes.snap && snapId !== 'npm:@metamask/institutional-wallet-snap') { + return 'snap'; + } + + return 'unknown'; +}; + +/** + * Checks if an account is an EVM account (EOA or ERC-4337) + */ +export const isEVMAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'evm'; +}; + +/** + * Checks if an account is a Solana account + */ +export const isSolanaAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'solana'; +}; + +/** + * Checks if an account is a hardware wallet account + */ +export const isHardwareAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'hardware'; +}; + +/** + * Checks if an account is a private key account + */ +export const isPrivateKeyAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'private-key'; +}; + +/** + * Checks if an account is an institutional EVM account + */ +export const isInstitutionalEVMAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'institutional-evm'; +}; + +/** + * Checks if an account is a Bitcoin account + */ +export const isBitcoinAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'bitcoin'; +}; + +/** + * Checks if an account is a snap account (non-institutional) + */ +export const isSnapAccount = (account: InternalAccount): boolean => { + return getAccountTypeCategory(account) === 'snap'; +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx new file mode 100644 index 00000000000..6856c780fa2 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppSliceState } from '../../../ducks/app/app'; +import { getInternalAccountByAddress } from '../../../selectors'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { AccountDetailsRow } from '../../../components/multichain-accounts/account-details-row'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { IconName } from '../../../components/component-library/icon'; +import { ButtonIcon, ButtonIconSize } from '../../../components/component-library'; +import { IconColor } from '../../../helpers/constants/design-system'; + +type EVMAccountDetailsProps = { + children?: React.ReactNode | React.ReactNode[]; +}; + +export const EVMAccountDetails = ({ children }: EVMAccountDetailsProps) => { + const address = useSelector( + (state: AppSliceState) => state.appState.accountDetailsAddress, + ); + const account = useSelector((state) => + getInternalAccountByAddress(state, address), + ); + const t = useI18nContext(); + + if (!account) { + return null; + } + + const { type } = account; + const isERC4337 = type === 'eip155:erc4337'; + + return ( + + {/* EVM-specific account details */} + {isERC4337 && ( + + )} + + {/* Add more EVM-specific rows here as needed */} + {/* For example: */} + {/* - Gas settings */} + {/* - Network-specific information */} + {/* - Contract interaction history */} + + {children} + + ); +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/index.ts b/ui/pages/multichain-accounts/account-details/index.ts new file mode 100644 index 00000000000..beafd7e4df7 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/index.ts @@ -0,0 +1,3 @@ +export { AccountDetails } from './account-details'; +export { EVMAccountDetails } from './evm-account-details'; +export * from './account-type-utils'; \ No newline at end of file From 92e1dd4fbcb943a83cf5aff0cc3e865f0463ec52 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:00:39 -0400 Subject: [PATCH 02/55] test: add mock account data --- test/data/mock-accounts.ts | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts index f8f5e86d428..6b6abfe4d6b 100644 --- a/test/data/mock-accounts.ts +++ b/test/data/mock-accounts.ts @@ -30,6 +30,51 @@ export const MOCK_ACCOUNT_EOA: InternalAccount = { }, }; +export const MOCK_ACCOUNT_PRIVATE_KEY: InternalAccount = { + id: 'd6ad74fa-ca5e-4d2d-ad53-95ababbfe872', + address: '0x2990079bcdee240329a520d2444386fc119da21a', + options: {}, + methods: [ + 'personal_sign', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + ], + scopes: [EthScope.Mainnet], + type: EthAccountType.Eoa, + metadata: { + name: 'Account 58', + importTime: 1750963640738, + keyring: { type: 'Simple Key Pair' }, + lastSelected: 1750963640759, + }, +}; + +export const MOCK_ACCOUNT_HARDWARE: InternalAccount = { + id: 'a4a41a3d-13d9-4ef3-be2f-aa28f47879aa', + address: '0x3a3fc52253e62cf4f3573814aa410736c9db5d0c', + options: {}, + methods: [ + 'personal_sign', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + ], + scopes: [EthScope.Mainnet], + type: EthAccountType.Eoa, + metadata: { + name: 'Ledger 2', + importTime: 1750963627141, + keyring: { type: KeyringTypes.ledger }, + lastSelected: 1750963627172, + nameLastUpdatedAt: 1750963627173, + }, +}; + export const MOCK_ACCOUNT_ERC4337: InternalAccount = { id: '4d5921f2-2022-44ce-a84f-9f6a0f142a5c', address: '0x123', @@ -97,4 +142,6 @@ export const MOCK_ACCOUNTS = { [MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH, [MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET.id]: MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET, [MOCK_ACCOUNT_SOLANA_MAINNET.id]: MOCK_ACCOUNT_SOLANA_MAINNET, + [MOCK_ACCOUNT_HARDWARE.id]: MOCK_ACCOUNT_HARDWARE, + [MOCK_ACCOUNT_PRIVATE_KEY.id]: MOCK_ACCOUNT_PRIVATE_KEY, }; From 2379206f85004c5f0a1012292d0f22335d51019d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:02:10 -0400 Subject: [PATCH 03/55] feat: add account details pages for various account types --- .../hardware-account-details.tsx | 18 ++++++++ .../institutional-evm-account-details.tsx | 16 +++++++ .../multichain-account-details.tsx | 46 +++++++++++++++++++ .../private-key-account-details.tsx | 0 .../solana-account-details.tsx | 15 ++++++ 5 files changed, 95 insertions(+) create mode 100644 ui/pages/multichain-accounts/account-details/hardware-account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/multichain-account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/private-key-account-details.tsx create mode 100644 ui/pages/multichain-accounts/account-details/solana-account-details.tsx diff --git a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx new file mode 100644 index 00000000000..b3610c1efae --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; + +type HardwareAccountDetailsProps = { + address: string; + account: InternalAccount; +}; + +export const HardwareAccountDetails = ({ address, account }: HardwareAccountDetailsProps) => { + + return ( + + + + ); +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx new file mode 100644 index 00000000000..b321c202445 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; + +type InstitutionalEVMAccountDetailsProps = { + address: string; + account: InternalAccount; +}; + +export const InstitutionalEVMAccountDetails = ({ address, account }: InstitutionalEVMAccountDetailsProps) => { + + return ( + + ); +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx new file mode 100644 index 00000000000..e8becc1290c --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppSliceState } from '../../../ducks/app/app'; +import { getInternalAccountByAddress } from '../../../selectors'; +import { EVMAccountDetails } from './evm-account-details'; +import { getAccountTypeCategory } from './account-type-utils'; +import { SolanaAccountDetails } from './solana-account-details'; +import { HardwareAccountDetails } from './hardware-account-details'; +import { PrivateKeyAccountDetails } from './private-key-account-details'; +import { InstitutionalEVMAccountDetails } from './institutional-evm-account-details'; + +type MultichainAccountDetailsProps = { + address: string; +}; + +export const MultichainAccountDetails = ({ address }: MultichainAccountDetailsProps) => { + const account = useSelector((state) => + getInternalAccountByAddress(state, address), + ); + + const accountTypeCategory = getAccountTypeCategory(account); + + const renderAccountDetailsByType = () => { + switch (accountTypeCategory) { + case 'evm': + return ; + + case 'solana': + return ; + + case 'hardware': + return ; + + case 'private-key': + return ; + + case 'institutional-evm': + return ; + + default: + return null; + } + }; + + return renderAccountDetailsByType(); +}; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ui/pages/multichain-accounts/account-details/solana-account-details.tsx b/ui/pages/multichain-accounts/account-details/solana-account-details.tsx new file mode 100644 index 00000000000..85f93dccfc2 --- /dev/null +++ b/ui/pages/multichain-accounts/account-details/solana-account-details.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { InternalAccount } from '@metamask/keyring-internal-api'; + +type SolanaAccountDetailsProps = { + address: string; + account: InternalAccount; +}; + +export const SolanaAccountDetails = ({ address, account }: SolanaAccountDetailsProps) => { + + return ( + + ); +}; \ No newline at end of file From 94abd2423ba0233410a802e0c4360891385f7252 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:02:43 -0400 Subject: [PATCH 04/55] feat: conditionally render account details based on state 1 flag --- ui/pages/routes/routes.component.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 3910a08d952..cfa318284c7 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -94,6 +94,7 @@ import { setTheme, showAppHeader, } from './utils'; +import { MultichainAccountDetails } from '../multichain-accounts/account-details'; // Begin Lazy Routes const OnboardingFlow = mmLazy(() => @@ -426,6 +427,19 @@ export default class Routes extends Component { return routes; } + renderAccountDetails() { + const { accountDetailsAddress, isMultichainAccountsState1Enabled } = + this.props; + if (!accountDetailsAddress) { + return null; + } + return isMultichainAccountsState1Enabled ? ( + + ) : ( + + ); + } + render() { const { isLoading, @@ -444,6 +458,7 @@ export default class Routes extends Component { accountDetailsAddress, isImportTokensModalOpen, isDeprecatedNetworkModalOpen, + isMultichainAccountsState1Enabled, location, isImportNftsModalOpen, hideImportNftsModal, @@ -559,9 +574,7 @@ export default class Routes extends Component { ) : null} - {accountDetailsAddress ? ( - - ) : null} + {this.renderAccountDetails()} {isImportNftsModalOpen ? ( ) : null} From f399be79b620124e15086f2216ae8e225771be12 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:03:37 -0400 Subject: [PATCH 05/55] test: update tests --- .../account-details/account-details.test.tsx | 16 +++++--- .../account-type-utils.test.ts | 39 +++++++------------ 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/account-details.test.tsx b/ui/pages/multichain-accounts/account-details/account-details.test.tsx index d19452cf5ae..8253fe0d9db 100644 --- a/ui/pages/multichain-accounts/account-details/account-details.test.tsx +++ b/ui/pages/multichain-accounts/account-details/account-details.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; @@ -67,7 +67,10 @@ describe('AccountDetails', () => { }); it('should render EVM account details for ERC-4337 accounts', () => { - const state = createMockState(MOCK_ACCOUNT_ERC4337.address, MOCK_ACCOUNT_ERC4337); + const state = createMockState( + MOCK_ACCOUNT_ERC4337.address, + MOCK_ACCOUNT_ERC4337, + ); const store = mockStore(state); renderWithProvider( @@ -81,8 +84,11 @@ describe('AccountDetails', () => { expect(screen.getByText('Account 2')).toBeInTheDocument(); }); - it('should render base account details for Solana accounts', () => { - const state = createMockState(MOCK_ACCOUNT_SOLANA_MAINNET.address, MOCK_ACCOUNT_SOLANA_MAINNET); + it('should render account details for Solana accounts', () => { + const state = createMockState( + MOCK_ACCOUNT_SOLANA_MAINNET.address, + MOCK_ACCOUNT_SOLANA_MAINNET, + ); const store = mockStore(state); renderWithProvider( @@ -110,4 +116,4 @@ describe('AccountDetails', () => { expect(mockPush).toHaveBeenCalledWith('/'); }); }); -}); \ No newline at end of file +}); diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts index 07914e638b5..2fe81f76f06 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts @@ -1,6 +1,8 @@ import { MOCK_ACCOUNT_EOA, MOCK_ACCOUNT_ERC4337, + MOCK_ACCOUNT_HARDWARE, + MOCK_ACCOUNT_PRIVATE_KEY, MOCK_ACCOUNT_SOLANA_MAINNET, } from '../../../../test/data/mock-accounts'; import { @@ -10,8 +12,6 @@ import { isHardwareAccount, isPrivateKeyAccount, isInstitutionalEVMAccount, - isBitcoinAccount, - isSnapAccount, } from './account-type-utils'; describe('Account Type Utils', () => { @@ -25,7 +25,9 @@ describe('Account Type Utils', () => { }); it('should return "solana" for Solana accounts', () => { - expect(getAccountTypeCategory(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe('solana'); + expect(getAccountTypeCategory(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe( + 'solana', + ); }); it('should return "unknown" for null/undefined accounts', () => { @@ -63,6 +65,10 @@ describe('Account Type Utils', () => { }); describe('isHardwareAccount', () => { + it('should return true for hardware accounts', () => { + expect(isHardwareAccount(MOCK_ACCOUNT_HARDWARE)).toBe(true); + }); + it('should return false for EOA accounts', () => { expect(isHardwareAccount(MOCK_ACCOUNT_EOA)).toBe(false); }); @@ -73,6 +79,10 @@ describe('Account Type Utils', () => { }); describe('isPrivateKeyAccount', () => { + it('should return true for private key accounts', () => { + expect(isPrivateKeyAccount(MOCK_ACCOUNT_PRIVATE_KEY)).toBe(true); + }); + it('should return false for EOA accounts', () => { expect(isPrivateKeyAccount(MOCK_ACCOUNT_EOA)).toBe(false); }); @@ -83,6 +93,7 @@ describe('Account Type Utils', () => { }); describe('isInstitutionalEVMAccount', () => { + // TODO: Add tests for institutional EVM accounts it('should return false for regular EOA accounts', () => { expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_EOA)).toBe(false); }); @@ -91,24 +102,4 @@ describe('Account Type Utils', () => { expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_ERC4337)).toBe(false); }); }); - - describe('isBitcoinAccount', () => { - it('should return false for EOA accounts', () => { - expect(isBitcoinAccount(MOCK_ACCOUNT_EOA)).toBe(false); - }); - - it('should return false for Solana accounts', () => { - expect(isBitcoinAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); - }); - }); - - describe('isSnapAccount', () => { - it('should return false for EOA accounts', () => { - expect(isSnapAccount(MOCK_ACCOUNT_EOA)).toBe(false); - }); - - it('should return false for Solana accounts', () => { - expect(isSnapAccount(MOCK_ACCOUNT_SOLANA_MAINNET)).toBe(false); - }); - }); -}); \ No newline at end of file +}); From 8a3447384446e28cdc67f4da2abd824c2efcc220 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:04:20 -0400 Subject: [PATCH 06/55] chore: rename account details page to multichain account details --- .../account-details/account-details.tsx | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 ui/pages/multichain-accounts/account-details/account-details.tsx diff --git a/ui/pages/multichain-accounts/account-details/account-details.tsx b/ui/pages/multichain-accounts/account-details/account-details.tsx deleted file mode 100644 index e896ba36983..00000000000 --- a/ui/pages/multichain-accounts/account-details/account-details.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useCallback, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { AppSliceState } from '../../../ducks/app/app'; -import { getInternalAccountByAddress } from '../../../selectors'; -import { setAccountDetailsAddress } from '../../../store/actions'; -import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; -import { EVMAccountDetails } from './evm-account-details'; -import { getAccountTypeCategory } from './account-type-utils'; - -// Import specific account type components (to be created) -// import { SolanaAccountDetails } from './solana-account-details'; -// import { HardwareAccountDetails } from './hardware-account-details'; -// import { PrivateKeyAccountDetails } from './private-key-account-details'; -// import { InstitutionalAccountDetails } from './institutional-account-details'; - -type AccountDetailsProps = { - children?: React.ReactNode | React.ReactNode[]; -}; - -export const AccountDetails = ({ children }: AccountDetailsProps) => { - const dispatch = useDispatch(); - const history = useHistory(); - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); - - const account = useSelector((state) => - getInternalAccountByAddress(state, address), - ); - - const handleNavigation = useCallback(() => { - dispatch(setAccountDetailsAddress('')); - history.push(DEFAULT_ROUTE); - }, [history, dispatch]); - - useEffect(() => { - if (!address) { - history.push(DEFAULT_ROUTE); - } - }, [dispatch, address, history]); - - if (!account) { - return null; - } - - const accountTypeCategory = getAccountTypeCategory(account); - - const renderAccountDetailsByType = () => { - switch (accountTypeCategory) { - case 'evm': - return {children}; - - case 'solana': - // TODO: Create SolanaAccountDetails component - return {children}; - - case 'hardware': - // TODO: Create HardwareAccountDetails component - return {children}; - - case 'private-key': - // TODO: Create PrivateKeyAccountDetails component - return {children}; - - case 'institutional-evm': - // TODO: Create InstitutionalAccountDetails component - return {children}; - - case 'bitcoin': - // TODO: Create BitcoinAccountDetails component - return {children}; - - case 'snap': - // TODO: Create SnapAccountDetails component - return {children}; - - default: - // Fallback to base account details for unknown types - return {children}; - } - }; - - return renderAccountDetailsByType(); -}; \ No newline at end of file From f7191e5b6f7cec8e875a44e2cb4a602ca4655a5d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:05:00 -0400 Subject: [PATCH 07/55] chore: remove extra account types from utils --- .../account-details/account-type-utils.ts | 68 +++++++------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.ts index fe3f974ea60..ee2d1061b90 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.ts @@ -1,9 +1,4 @@ -import { - EthAccountType, - BtcAccountType, - SolAccountType, - isEvmAccountType -} from '@metamask/keyring-api'; +import { SolAccountType, isEvmAccountType } from '@metamask/keyring-api'; import { KeyringTypes } from '@metamask/keyring-controller'; import { InternalAccount } from '@metamask/keyring-internal-api'; @@ -13,37 +8,36 @@ export type AccountTypeCategory = | 'hardware' | 'private-key' | 'institutional-evm' - | 'bitcoin' - | 'snap' | 'unknown'; /** * Determines the account type category based on the account's type and keyring information */ -export const getAccountTypeCategory = (account: InternalAccount): AccountTypeCategory => { +export const getAccountTypeCategory = ( + account: InternalAccount, +): AccountTypeCategory => { if (!account) return 'unknown'; const { type, metadata } = account; - const keyringType = metadata?.keyring?.type; + const keyringType = metadata?.keyring?.type as KeyringTypes; const snapId = metadata?.snap?.id; // EVM accounts (EOA and ERC-4337) if (isEvmAccountType(type)) { - // Check if it's an institutional account (through institutional snap) - if (snapId === 'npm:@metamask/institutional-wallet-snap') { - return 'institutional-evm'; - } return 'evm'; } // Hardware accounts - if (keyringType && [ - KeyringTypes.ledger, - KeyringTypes.trezor, - KeyringTypes.oneKey, - KeyringTypes.lattice, - KeyringTypes.qr - ].includes(keyringType)) { + if ( + keyringType && + [ + KeyringTypes.ledger, + KeyringTypes.trezor, + KeyringTypes.oneKey, + KeyringTypes.lattice, + KeyringTypes.qr, + ].includes(keyringType) + ) { return 'hardware'; } @@ -57,14 +51,12 @@ export const getAccountTypeCategory = (account: InternalAccount): AccountTypeCat return 'solana'; } - // Bitcoin accounts - if (Object.values(BtcAccountType).includes(type)) { - return 'bitcoin'; - } - - // Snap accounts (non-institutional) - if (keyringType === KeyringTypes.snap && snapId !== 'npm:@metamask/institutional-wallet-snap') { - return 'snap'; + // Institutional-EVM accounts + if ( + keyringType === KeyringTypes.snap && + snapId === 'npm:@metamask/institutional-wallet-snap' + ) { + return 'institutional-evm'; } return 'unknown'; @@ -101,20 +93,8 @@ export const isPrivateKeyAccount = (account: InternalAccount): boolean => { /** * Checks if an account is an institutional EVM account */ -export const isInstitutionalEVMAccount = (account: InternalAccount): boolean => { +export const isInstitutionalEVMAccount = ( + account: InternalAccount, +): boolean => { return getAccountTypeCategory(account) === 'institutional-evm'; }; - -/** - * Checks if an account is a Bitcoin account - */ -export const isBitcoinAccount = (account: InternalAccount): boolean => { - return getAccountTypeCategory(account) === 'bitcoin'; -}; - -/** - * Checks if an account is a snap account (non-institutional) - */ -export const isSnapAccount = (account: InternalAccount): boolean => { - return getAccountTypeCategory(account) === 'snap'; -}; \ No newline at end of file From 4a250be2381593f1084d729ae9ff88e67d4f1de4 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:06:02 -0400 Subject: [PATCH 08/55] chore: update exports --- ui/pages/multichain-accounts/account-details/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/index.ts b/ui/pages/multichain-accounts/account-details/index.ts index beafd7e4df7..bfcc56ea8ca 100644 --- a/ui/pages/multichain-accounts/account-details/index.ts +++ b/ui/pages/multichain-accounts/account-details/index.ts @@ -1,3 +1,7 @@ -export { AccountDetails } from './account-details'; +export { MultichainAccountDetails } from './multichain-account-details'; export { EVMAccountDetails } from './evm-account-details'; -export * from './account-type-utils'; \ No newline at end of file +export { SolanaAccountDetails } from './solana-account-details'; +export { HardwareAccountDetails } from './hardware-account-details'; +export { PrivateKeyAccountDetails } from './private-key-account-details'; +export { InstitutionalEVMAccountDetails } from './institutional-evm-account-details'; +export * from './account-type-utils'; From 164d8ce7be543a8917015c96cd7e5c4712329e29 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:06:44 -0400 Subject: [PATCH 09/55] refactor: remove unnecessary code from evm account details --- .../account-details/evm-account-details.tsx | 61 ++++--------------- 1 file changed, 11 insertions(+), 50 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx index 6856c780fa2..4a1c8bb6392 100644 --- a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx @@ -1,59 +1,20 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { AppSliceState } from '../../../ducks/app/app'; -import { getInternalAccountByAddress } from '../../../selectors'; import { BaseAccountDetails } from '../base-account-details/base-account-details'; -import { AccountDetailsRow } from '../../../components/multichain-accounts/account-details-row'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { IconName } from '../../../components/component-library/icon'; -import { ButtonIcon, ButtonIconSize } from '../../../components/component-library'; -import { IconColor } from '../../../helpers/constants/design-system'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type EVMAccountDetailsProps = { - children?: React.ReactNode | React.ReactNode[]; + address: string; + account: InternalAccount; }; -export const EVMAccountDetails = ({ children }: EVMAccountDetailsProps) => { - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); - const account = useSelector((state) => - getInternalAccountByAddress(state, address), - ); - const t = useI18nContext(); - - if (!account) { - return null; - } - - const { type } = account; - const isERC4337 = type === 'eip155:erc4337'; - +export const EVMAccountDetails = ({ + address, + account, +}: EVMAccountDetailsProps) => { return ( - - {/* EVM-specific account details */} - {isERC4337 && ( - - )} - - {/* Add more EVM-specific rows here as needed */} - {/* For example: */} - {/* - Gas settings */} - {/* - Network-specific information */} - {/* - Contract interaction history */} - - {children} + + ); -}; \ No newline at end of file +}; From f1b74a329b03a75a5a8dcd967777930c532a843b Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:07:45 -0400 Subject: [PATCH 10/55] feat: move selectors into props, add useEffect for navigation, update styling --- .../base-account-details.tsx | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx index ef6fd38b38f..90a63cec004 100644 --- a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { isEvmAccountType } from '@metamask/keyring-api'; -import { AppSliceState } from '../../../ducks/app/app'; -import { getInternalAccountByAddress, getUseBlockie } from '../../../selectors'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { getUseBlockie } from '../../../selectors'; import { AvatarAccount, AvatarAccountSize, @@ -31,21 +31,23 @@ import { shortenAddress } from '../../../helpers/utils/util'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { AccountDetailsRow } from '../../../components/multichain-accounts/account-details-row'; import { EditAccountNameModal } from '../../../components/multichain-accounts/edit-account-name-modal'; +import { setAccountDetailsAddress } from '../../../store/actions'; type BaseAccountDetailsProps = { children?: React.ReactNode | React.ReactNode[]; + address: string; + account: InternalAccount; }; -export const BaseAccountDetails = ({ children }: BaseAccountDetailsProps) => { - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); +export const BaseAccountDetails = ({ + children, + address, + account, +}: BaseAccountDetailsProps) => { const useBlockie = useSelector(getUseBlockie); const history = useHistory(); + const dispatch = useDispatch(); const t = useI18nContext(); - const account = useSelector((state) => - getInternalAccountByAddress(state, address), - ); const { metadata: { name }, type, @@ -61,6 +63,17 @@ export const BaseAccountDetails = ({ children }: BaseAccountDetailsProps) => { history.push(ACCOUNT_DETAILS_QR_CODE_ROUTE); }; + const handleNavigation = useCallback(() => { + dispatch(setAccountDetailsAddress('')); + history.push(DEFAULT_ROUTE); + }, [history, dispatch]); + + useEffect(() => { + if (!address) { + history.push(DEFAULT_ROUTE); + } + }, [dispatch, address, history]); + return (
{ ariaLabel="Back" iconName={IconName.ArrowLeft} size={ButtonIconSize.Sm} - onClick={() => history.push(DEFAULT_ROUTE)} + onClick={handleNavigation} /> } > {name}
- + Date: Thu, 26 Jun 2025 16:29:53 -0400 Subject: [PATCH 11/55] feat: add private key account details page --- .../private-key-account-details.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx index e69de29bb2d..a073d829828 100644 --- a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; + +type PrivateKeyAccountDetailsProps = { + address: string; + account: InternalAccount; +}; + +export const PrivateKeyAccountDetails = ({ address, account }: PrivateKeyAccountDetailsProps) => { + + return ( + + + + ); +}; \ No newline at end of file From 6862014bb88da8e7af39ca3d870b027e16f66b4d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:50:45 -0400 Subject: [PATCH 12/55] test: fix account details tests --- ...sx => multichain-account-details.test.tsx} | 90 ++++++++++++++++--- 1 file changed, 79 insertions(+), 11 deletions(-) rename ui/pages/multichain-accounts/account-details/{account-details.test.tsx => multichain-account-details.test.tsx} (55%) diff --git a/ui/pages/multichain-accounts/account-details/account-details.test.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx similarity index 55% rename from ui/pages/multichain-accounts/account-details/account-details.test.tsx rename to ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx index 8253fe0d9db..373c64d0d62 100644 --- a/ui/pages/multichain-accounts/account-details/account-details.test.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx @@ -9,7 +9,7 @@ import { MOCK_ACCOUNT_ERC4337, MOCK_ACCOUNT_SOLANA_MAINNET, } from '../../../../test/data/mock-accounts'; -import { AccountDetails } from './account-details'; +import { MultichainAccountDetails } from './multichain-account-details'; const middleware = [thunk]; const mockStore = configureMockStore(middleware); @@ -32,6 +32,9 @@ const createMockState = (address: string, account = MOCK_ACCOUNT_EOA) => ({ appState: { accountDetailsAddress: address, }, + activeTab: { + origin: 'test', + }, metamask: { internalAccounts: { accounts: { @@ -42,6 +45,66 @@ const createMockState = (address: string, account = MOCK_ACCOUNT_EOA) => ({ }, selectedAccount: account.id, }, + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + name: 'Ethereum Mainnet', + nativeCurrency: 'ETH', + rpcEndpoints: [ + { + networkClientId: 'mainnet', + url: 'https://mainnet.infura.io/v3/', + type: 'infura', + }, + ], + defaultRpcEndpointIndex: 0, + blockExplorerUrls: ['https://etherscan.io'], + defaultBlockExplorerUrlIndex: 0, + }, + }, + selectedNetworkClientId: 'mainnet', + networksMetadata: { + mainnet: { + status: 'available', + }, + }, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [address], + metadata: { + id: 'keyring1', + name: 'HD Key Tree', + }, + }, + ], + accountTree: { + wallets: { + 'wallet:1': { + metadata: { + name: 'Wallet 1', + }, + groups: { + 'group:1': { + metadata: { + name: 'Group 1', + }, + accounts: [account.id], + }, + }, + }, + }, + }, + accountsByChainId: { + '0x1': { + [address]: { + balance: '0x0', + }, + }, + }, + pinnedAccounts: [], + hiddenAccounts: [], + permissionHistory: {}, }, }); @@ -57,13 +120,14 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); - // Should render the base account details (which includes account name) - expect(screen.getByText('Account 1')).toBeInTheDocument(); + // Should render the base account details (which includes account name in header and details) + const accountNameElements = screen.getAllByText('Account 1'); + expect(accountNameElements).toHaveLength(2); }); it('should render EVM account details for ERC-4337 accounts', () => { @@ -75,13 +139,14 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); - // Should render the base account details (which includes account name) - expect(screen.getByText('Account 2')).toBeInTheDocument(); + // Should render the base account details (which includes account name in header and details) + const accountNameElements = screen.getAllByText('Account 2'); + expect(accountNameElements).toHaveLength(2); }); it('should render account details for Solana accounts', () => { @@ -93,13 +158,16 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); - // Should render the base account details (which includes account name) - expect(screen.getByText('Solana Account')).toBeInTheDocument(); + // Should render the base account details (which includes account name in header and details) + const accountNameElements = screen.getAllByText('Solana Account'); + expect(accountNameElements).toHaveLength(2); }); it('should navigate to default route when no address is provided', () => { @@ -108,7 +176,7 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); From cad293fac5d7ea1f8fbdfb0d4f65afaba86040fa Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 16:54:00 -0400 Subject: [PATCH 13/55] fix: update mock accounts and fix utils --- test/data/mock-accounts.ts | 2 +- .../account-details/account-type-utils.ts | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts index 6b6abfe4d6b..9de02cd217f 100644 --- a/test/data/mock-accounts.ts +++ b/test/data/mock-accounts.ts @@ -47,7 +47,7 @@ export const MOCK_ACCOUNT_PRIVATE_KEY: InternalAccount = { metadata: { name: 'Account 58', importTime: 1750963640738, - keyring: { type: 'Simple Key Pair' }, + keyring: { type: KeyringTypes.simple }, lastSelected: 1750963640759, }, }; diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.ts index ee2d1061b90..9f0ff62a41c 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.ts @@ -22,12 +22,7 @@ export const getAccountTypeCategory = ( const keyringType = metadata?.keyring?.type as KeyringTypes; const snapId = metadata?.snap?.id; - // EVM accounts (EOA and ERC-4337) - if (isEvmAccountType(type)) { - return 'evm'; - } - - // Hardware accounts + // Hardware accounts (must be checked before EVM check) if ( keyringType && [ @@ -41,17 +36,12 @@ export const getAccountTypeCategory = ( return 'hardware'; } - // Private key accounts + // Private key accounts (must be checked before EVM check) if (keyringType === KeyringTypes.simple) { return 'private-key'; } - // Solana accounts - if (type === SolAccountType.DataAccount) { - return 'solana'; - } - - // Institutional-EVM accounts + // Institutional-EVM accounts (must be checked before EVM check) if ( keyringType === KeyringTypes.snap && snapId === 'npm:@metamask/institutional-wallet-snap' @@ -59,6 +49,16 @@ export const getAccountTypeCategory = ( return 'institutional-evm'; } + // EVM accounts (EOA and ERC-4337) - general fallback + if (isEvmAccountType(type)) { + return 'evm'; + } + + // Solana accounts + if (type === SolAccountType.DataAccount) { + return 'solana'; + } + return 'unknown'; }; From 5e1edbb66aad26aee6cc135775ce542e42a48ce2 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:12:44 -0400 Subject: [PATCH 14/55] chore: remove unused props --- ui/pages/routes/routes.component.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 53876f9dc12..6229fec4bc7 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -464,10 +464,8 @@ export default class Routes extends Component { isAccountMenuOpen, toggleAccountMenu, isNetworkMenuOpen, - accountDetailsAddress, isImportTokensModalOpen, isDeprecatedNetworkModalOpen, - isMultichainAccountsState1Enabled, location, isImportNftsModalOpen, hideImportNftsModal, From 9501960d310331e308393187ab1aeb927ee0f962 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:36:55 -0400 Subject: [PATCH 15/55] feat: add multichain account details route --- ui/pages/routes/routes.component.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 6229fec4bc7..4f0616221bb 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -55,6 +55,7 @@ import { DEEP_LINK_ROUTE, SMART_ACCOUNT_UPDATE, WALLET_DETAILS_ROUTE, + ACCOUNT_DETAILS_ROUTE, } from '../../helpers/constants/routes'; import { @@ -419,6 +420,11 @@ export default class Routes extends Component { component={WalletDetails} exact /> + @@ -439,14 +445,10 @@ export default class Routes extends Component { renderAccountDetails() { const { accountDetailsAddress, isMultichainAccountsState1Enabled } = this.props; - if (!accountDetailsAddress) { + if (!accountDetailsAddress || isMultichainAccountsState1Enabled) { return null; } - return isMultichainAccountsState1Enabled ? ( - - ) : ( - - ); + return ; } render() { @@ -489,6 +491,8 @@ export default class Routes extends Component { ///: END:ONLY_INCLUDE_IF } = this.props; + console.log(location); + const loadMessage = loadingMessage || isNetworkLoading ? getConnectingLabel(loadingMessage, this.props, this.context) From 525fb8d329786fcef3720e54fdd75d40db68f183 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:37:34 -0400 Subject: [PATCH 16/55] feat: add logic to conditionally push to multichain account details route based on feature flag --- .../menu-items/account-details-menu-item.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/components/multichain/menu-items/account-details-menu-item.js b/ui/components/multichain/menu-items/account-details-menu-item.js index ec555029994..02a045a8a21 100644 --- a/ui/components/multichain/menu-items/account-details-menu-item.js +++ b/ui/components/multichain/menu-items/account-details-menu-item.js @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { setAccountDetailsAddress } from '../../../store/actions'; import { MenuItem } from '../../ui/menu'; @@ -13,6 +14,8 @@ import { } from '../../../../shared/constants/metametrics'; import { IconName, Text } from '../../component-library'; import { getHDEntropyIndex } from '../../../selectors/selectors'; +import { getIsMultichainAccountsState1Enabled } from '../../../selectors/multichain-accounts/feature-flags'; +import { ACCOUNT_DETAILS_ROUTE } from '../../../helpers/constants/routes'; export const AccountDetailsMenuItem = ({ metricsLocation, @@ -24,7 +27,10 @@ export const AccountDetailsMenuItem = ({ const dispatch = useDispatch(); const trackEvent = useContext(MetaMetricsContext); const hdEntropyIndex = useSelector(getHDEntropyIndex); - + const history = useHistory(); + const isMultichainAccountsState1Enabled = useSelector( + getIsMultichainAccountsState1Enabled, + ); const LABEL = t('accountDetails'); return ( @@ -39,6 +45,9 @@ export const AccountDetailsMenuItem = ({ hd_entropy_index: hdEntropyIndex, }, }); + if (isMultichainAccountsState1Enabled) { + history.push(ACCOUNT_DETAILS_ROUTE); + } closeMenu?.(); }} iconName={IconName.ScanBarcode} From 0cd26c035901ed7e2befa729e495466edb109d97 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:38:04 -0400 Subject: [PATCH 17/55] feat: add logic to prevent app header from appearing for account details --- ui/pages/routes/utils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/pages/routes/utils.js b/ui/pages/routes/utils.js index 18c136c2316..4bb47b056f8 100644 --- a/ui/pages/routes/utils.js +++ b/ui/pages/routes/utils.js @@ -24,6 +24,7 @@ import { SNAPS_VIEW_ROUTE, DEEP_LINK_ROUTE, WALLET_DETAILS_ROUTE, + ACCOUNT_DETAILS_ROUTE, } from '../../helpers/constants/routes'; export function isConfirmTransactionRoute(pathname) { @@ -224,6 +225,17 @@ export function hideAppHeader(props) { return true; } + const isMultichainAccountDetailsPage = Boolean( + matchPath(location.pathname, { + path: ACCOUNT_DETAILS_ROUTE, + exact: false, + }), + ); + + if (isMultichainAccountDetailsPage) { + return true; + } + const isHandlingAddEthereumChainRequest = Boolean( matchPath(location.pathname, { path: CONFIRMATION_V_NEXT_ROUTE, From 1d1dbd64d37b01aca12280371fcf66212c9c0b7d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:39:21 -0400 Subject: [PATCH 18/55] feat: add accountDetailsAddress selector back to multichain account details --- .../account-details/multichain-account-details.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx index e8becc1290c..f5607d36d8a 100644 --- a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx @@ -9,11 +9,10 @@ import { HardwareAccountDetails } from './hardware-account-details'; import { PrivateKeyAccountDetails } from './private-key-account-details'; import { InstitutionalEVMAccountDetails } from './institutional-evm-account-details'; -type MultichainAccountDetailsProps = { - address: string; -}; - -export const MultichainAccountDetails = ({ address }: MultichainAccountDetailsProps) => { +export const MultichainAccountDetails = () => { + const address = useSelector( + (state: AppSliceState) => state.appState.accountDetailsAddress, + ); const account = useSelector((state) => getInternalAccountByAddress(state, address), ); From f5b06372d807b4a39d46c86fcf9e2a1133faf5db Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:41:01 -0400 Subject: [PATCH 19/55] feat: add logic to prevent navigation to new account details page if state 1 flag is not active --- .../base-account-details/base-account-details.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx index 3d41b79bb68..556446c9815 100644 --- a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx @@ -3,7 +3,10 @@ import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { isEvmAccountType } from '@metamask/keyring-api'; import { InternalAccount } from '@metamask/keyring-internal-api'; -import { getUseBlockie } from '../../../selectors'; +import { + getIsMultichainAccountsState1Enabled, + getUseBlockie, +} from '../../../selectors'; import { AvatarAccount, AvatarAccountSize, @@ -52,6 +55,9 @@ export const BaseAccountDetails = ({ const history = useHistory(); const dispatch = useDispatch(); const t = useI18nContext(); + const isMultichainAccountsState1Enabled = useSelector( + getIsMultichainAccountsState1Enabled, + ); const { metadata: { name }, type, @@ -73,10 +79,11 @@ export const BaseAccountDetails = ({ }, [history, dispatch]); useEffect(() => { - if (!address) { + if (!address || !isMultichainAccountsState1Enabled) { history.push(DEFAULT_ROUTE); } - }, [dispatch, address, history]); + }, [dispatch, address, history, isMultichainAccountsState1Enabled]); + // we can never have a scenario where an account is not associated with a wallet. const { id: walletId, name: walletName } = useSelector((state) => getWalletIdAndNameByAccountAddress(state, address), From 6ebf8b470bbb4e697499e3c56bfeb10beaf50cdc Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:47:05 -0400 Subject: [PATCH 20/55] feat: add logic to remove app header for address qr code page --- ui/pages/routes/utils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/pages/routes/utils.js b/ui/pages/routes/utils.js index 4bb47b056f8..30376c96e40 100644 --- a/ui/pages/routes/utils.js +++ b/ui/pages/routes/utils.js @@ -25,6 +25,7 @@ import { DEEP_LINK_ROUTE, WALLET_DETAILS_ROUTE, ACCOUNT_DETAILS_ROUTE, + ACCOUNT_DETAILS_QR_CODE_ROUTE, } from '../../helpers/constants/routes'; export function isConfirmTransactionRoute(pathname) { @@ -236,6 +237,17 @@ export function hideAppHeader(props) { return true; } + const isMultichainAccountDetailsQRCodePage = Boolean( + matchPath(location.pathname, { + path: ACCOUNT_DETAILS_QR_CODE_ROUTE, + exact: false, + }), + ); + + if (isMultichainAccountDetailsQRCodePage) { + return true; + } + const isHandlingAddEthereumChainRequest = Boolean( matchPath(location.pathname, { path: CONFIRMATION_V_NEXT_ROUTE, From 43d47104d3f9a73617cff836b4d10338ce26bbec Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:47:25 -0400 Subject: [PATCH 21/55] feat: add navigation protection for address qr code page --- .../address-qr-code/address-qr-code.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx index e5495a32ee8..a43c81f6cb2 100644 --- a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useEffect } from 'react'; import { parseCaipChainId } from '@metamask/utils'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -33,6 +33,7 @@ import { MetaMetricsEventName, MetaMetricsEventCategory, } from '../../../../shared/constants/metametrics'; +import { getIsMultichainAccountsState1Enabled } from '../../../selectors/multichain-accounts/feature-flags'; export const AddressQRCode = () => { const t = useI18nContext(); @@ -44,6 +45,9 @@ export const AddressQRCode = () => { const account = useSelector((state) => getInternalAccountByAddress(state, address), ); + const isMultichainAccountsState1Enabled = useSelector( + getIsMultichainAccountsState1Enabled, + ); const multichainNetwork = useMultichainSelector( getMultichainNetwork, @@ -71,6 +75,12 @@ export const AddressQRCode = () => { openBlockExplorer(addressLink, metricsLocation, trackEvent); }, [chainId, trackEvent, addressLink]); + useEffect(() => { + if (!address || isMultichainAccountsState1Enabled) { + history.push(ACCOUNT_DETAILS_ROUTE); + } + }, [address, history, isMultichainAccountsState1Enabled]); + return (
Date: Thu, 26 Jun 2025 17:47:46 -0400 Subject: [PATCH 22/55] feat: add address qr code route --- ui/pages/routes/routes.component.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 4f0616221bb..d7f39c76ef7 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -56,6 +56,7 @@ import { SMART_ACCOUNT_UPDATE, WALLET_DETAILS_ROUTE, ACCOUNT_DETAILS_ROUTE, + ACCOUNT_DETAILS_QR_CODE_ROUTE, } from '../../helpers/constants/routes'; import { @@ -89,6 +90,7 @@ import { } from '../../../shared/lib/confirmation.utils'; import { MultichainAccountListMenu } from '../../components/multichain-accounts/multichain-account-list-menu'; import { SmartAccountUpdate } from '../confirmations/components/confirm/smart-account-update'; +import { MultichainAccountDetails } from '../multichain-accounts/account-details'; import { getConnectingLabel, hideAppHeader, @@ -96,7 +98,7 @@ import { setTheme, showAppHeader, } from './utils'; -import { MultichainAccountDetails } from '../multichain-accounts/account-details'; +import { AddressQRCode } from '../multichain-accounts/address-qr-code'; // Begin Lazy Routes const OnboardingFlow = mmLazy(() => @@ -425,6 +427,11 @@ export default class Routes extends Component { component={MultichainAccountDetails} exact /> + From 14f3f493cca2d47399be21a3f1614425f8289bb6 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 17:53:05 -0400 Subject: [PATCH 23/55] fix: update condition to be navigate protect when flag is NOT enabled --- .../multichain-accounts/address-qr-code/address-qr-code.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx index a43c81f6cb2..e24256aa922 100644 --- a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx @@ -76,7 +76,7 @@ export const AddressQRCode = () => { }, [chainId, trackEvent, addressLink]); useEffect(() => { - if (!address || isMultichainAccountsState1Enabled) { + if (!address || !isMultichainAccountsState1Enabled) { history.push(ACCOUNT_DETAILS_ROUTE); } }, [address, history, isMultichainAccountsState1Enabled]); From f0b252c5feff3f52874cb2a4badddc2e52f4205f Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 18:06:20 -0400 Subject: [PATCH 24/55] feat: update smart contract account toggle components to use address props --- .../smart-contract-account-toggle-section.test.tsx | 5 ++++- .../smart-contract-account-toggle-section.tsx | 12 +++++++----- .../smart-contract-account-toggle.tsx | 10 ++++++---- .../account-details/hardware-account-details.tsx | 2 +- .../account-details/private-key-account-details.tsx | 2 +- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx index f8dee5d8783..4eb170a1e42 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx @@ -79,7 +79,10 @@ const render = (stateOverride = {}) => { ...mockState, ...stateOverride, }); - return renderWithProvider(, store); + return renderWithProvider( + , + store, + ); }; describe('SmartContractAccountToggleSection', () => { diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx index de3cfa81b27..99dc6822f76 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx @@ -13,15 +13,17 @@ import { } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; -import { AppSliceState } from '../../../ducks/app/app'; import { useEIP7702Networks } from '../../../pages/confirmations/hooks/useEIP7702Networks'; import { SmartContractAccountToggle } from '../smart-contract-account-toggle'; import Preloader from '../../ui/icon/preloader'; -export const SmartContractAccountToggleSection = () => { - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); +type SmartContractAccountToggleSectionProps = { + address: Hex; +}; + +export const SmartContractAccountToggleSection = ({ + address, +}: SmartContractAccountToggleSectionProps) => { const t = useI18nContext(); const { network7702List, pending } = useEIP7702Networks(address); diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx index 4f49cc44e0d..e57e63760bc 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx @@ -12,13 +12,15 @@ import { } from '../../../helpers/constants/design-system'; import { EIP7702NetworkConfiguration } from '../../../pages/confirmations/hooks/useEIP7702Networks'; +type SmartContractAccountToggleProps = { + networkConfig: EIP7702NetworkConfiguration; + address: string; +}; + export const SmartContractAccountToggle = ({ networkConfig, address, -}: { - networkConfig: EIP7702NetworkConfiguration; - address: Hex; -}) => { +}: SmartContractAccountToggleProps) => { const { name, isSupported, upgradeContractAddress, chainIdHex } = networkConfig; const { downgradeAccount, upgradeAccount, isUpgraded } = useEIP7702Account({ diff --git a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx index b3610c1efae..f78316d9d66 100644 --- a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx @@ -12,7 +12,7 @@ export const HardwareAccountDetails = ({ address, account }: HardwareAccountDeta return ( - + ); }; \ No newline at end of file diff --git a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx index a073d829828..ce80a84a3ec 100644 --- a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx @@ -12,7 +12,7 @@ export const PrivateKeyAccountDetails = ({ address, account }: PrivateKeyAccount return ( - + ); }; \ No newline at end of file From 0951f7ee41020ccd939b966bfb62c2b087f9a7f7 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 18:07:17 -0400 Subject: [PATCH 25/55] fix: update prop type --- .../smart-contract-account-toggle-section.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx index 99dc6822f76..202f5b32f03 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; import { Box, ButtonLink, ButtonLinkSize, Text } from '../../component-library'; import { @@ -18,7 +17,7 @@ import { SmartContractAccountToggle } from '../smart-contract-account-toggle'; import Preloader from '../../ui/icon/preloader'; type SmartContractAccountToggleSectionProps = { - address: Hex; + address: string; }; export const SmartContractAccountToggleSection = ({ From 922e4c20666119cbee0b89da6f464c0d72a0c33f Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 18:08:06 -0400 Subject: [PATCH 26/55] feat: add address prop to smart contract account toggle section component in evm account details --- .../multichain-accounts/account-details/evm-account-details.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx index 4a1c8bb6392..498758bb7d9 100644 --- a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx @@ -14,7 +14,7 @@ export const EVMAccountDetails = ({ }: EVMAccountDetailsProps) => { return ( - + ); }; From 646da78c833b0764a0fed99b361ce881569f6d4d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 19:24:24 -0400 Subject: [PATCH 27/55] chore: update types --- .../smart-contract-account-toggle-section.tsx | 3 +-- .../smart-contract-account-toggle.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx index 202f5b32f03..496a63ac3f3 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Hex } from '@metamask/utils'; import { Box, ButtonLink, ButtonLinkSize, Text } from '../../component-library'; import { AlignItems, @@ -46,7 +45,7 @@ export const SmartContractAccountToggleSection = ({ ))} diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx index e57e63760bc..816db00e34b 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx @@ -32,7 +32,7 @@ export const SmartContractAccountToggle = ({ const prevHasPendingRequests = useRef(); const { hasPendingRequests } = useBatchAuthorizationRequests( - address, + address as Hex, chainIdHex, ); @@ -40,7 +40,7 @@ export const SmartContractAccountToggle = ({ useEffect(() => { const checkUpgradeStatus = async () => { try { - const upgraded = await isUpgraded(address); + const upgraded = await isUpgraded(address as Hex); setAddressSupportSmartAccount(upgraded); } catch (error) { // Fall back to isSupported if we can't determine upgrade status @@ -66,9 +66,9 @@ export const SmartContractAccountToggle = ({ // Dispatch the transaction if (addressSupportSmartAccount) { - await downgradeAccount(address); + await downgradeAccount(address as Hex); } else if (upgradeContractAddress) { - await upgradeAccount(address, upgradeContractAddress); + await upgradeAccount(address as Hex, upgradeContractAddress); } }, [ address, From c926716bbdd0f0cccda7b049abdfd36c3e0313fd Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 20:10:50 -0400 Subject: [PATCH 28/55] fix: update storybook file with correct props --- .../base-account-details.stories.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.stories.tsx b/ui/pages/multichain-accounts/base-account-details/base-account-details.stories.tsx index 74d9c0068e6..33b95711f9b 100644 --- a/ui/pages/multichain-accounts/base-account-details/base-account-details.stories.tsx +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.stories.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { EthAccountType, SolAccountType } from '@metamask/keyring-api'; import { KeyringTypes } from '@metamask/keyring-controller'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import configureStore from '../../../store/store'; import { Box } from '../../../components/component-library'; import { BaseAccountDetails } from './base-account-details'; @@ -26,8 +27,9 @@ const MOCK_ETH_ACCOUNT = { 'eth_signTypedData_v3', 'eth_signTypedData_v4', ], + scopes: ['eip155:1'], type: EthAccountType.Eoa, -}; +} as InternalAccount; // Mock Solana Account const MOCK_SOLANA_ACCOUNT = { @@ -53,8 +55,9 @@ const MOCK_SOLANA_ACCOUNT = { 'solana_signAllTransactions', 'solana_signMessage', ], + scopes: ['solana:mainnet'] as const, type: SolAccountType.DataAccount, -}; +} as InternalAccount; // Minimal mock store data const createBaseMockStore = (account, address, walletName = 'Mock Wallet') => ({ @@ -196,7 +199,10 @@ export default { export const EthereumAccount = { render: () => ( - + ), }; @@ -205,7 +211,10 @@ export const EthereumAccount = { export const SolanaAccount = { render: () => ( - + ), }; \ No newline at end of file From 02548a599c8c3f21eb659bf7899bfa2b58cbb34c Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 26 Jun 2025 20:11:00 -0400 Subject: [PATCH 29/55] fix: resolve lint errors --- .../account-details/account-type-utils.ts | 16 +++++++++++++++- .../account-details/evm-account-details.tsx | 2 +- .../account-details/hardware-account-details.tsx | 10 ++++++---- .../institutional-evm-account-details.tsx | 15 +++++++-------- .../multichain-account-details.test.tsx | 10 ++++------ .../multichain-account-details.tsx | 6 ++++-- .../private-key-account-details.tsx | 10 ++++++---- .../account-details/solana-account-details.tsx | 14 +++++++------- ui/pages/routes/routes.component.js | 2 -- 9 files changed, 50 insertions(+), 35 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.ts index 9f0ff62a41c..e782e06780d 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.ts @@ -12,11 +12,15 @@ export type AccountTypeCategory = /** * Determines the account type category based on the account's type and keyring information + * + * @param account */ export const getAccountTypeCategory = ( account: InternalAccount, ): AccountTypeCategory => { - if (!account) return 'unknown'; + if (!account) { + return 'unknown'; + } const { type, metadata } = account; const keyringType = metadata?.keyring?.type as KeyringTypes; @@ -64,6 +68,8 @@ export const getAccountTypeCategory = ( /** * Checks if an account is an EVM account (EOA or ERC-4337) + * + * @param account */ export const isEVMAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'evm'; @@ -71,6 +77,8 @@ export const isEVMAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a Solana account + * + * @param account */ export const isSolanaAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'solana'; @@ -78,6 +86,8 @@ export const isSolanaAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a hardware wallet account + * + * @param account */ export const isHardwareAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'hardware'; @@ -85,6 +95,8 @@ export const isHardwareAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a private key account + * + * @param account */ export const isPrivateKeyAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'private-key'; @@ -92,6 +104,8 @@ export const isPrivateKeyAccount = (account: InternalAccount): boolean => { /** * Checks if an account is an institutional EVM account + * + * @param account */ export const isInstitutionalEVMAccount = ( account: InternalAccount, diff --git a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx index 498758bb7d9..3a6e3bbdea9 100644 --- a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { InternalAccount } from '@metamask/keyring-internal-api'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type EVMAccountDetailsProps = { diff --git a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx index f78316d9d66..4e91c5b557b 100644 --- a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { InternalAccount } from '@metamask/keyring-internal-api'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type HardwareAccountDetailsProps = { @@ -8,11 +8,13 @@ type HardwareAccountDetailsProps = { account: InternalAccount; }; -export const HardwareAccountDetails = ({ address, account }: HardwareAccountDetailsProps) => { - +export const HardwareAccountDetails = ({ + address, + account, +}: HardwareAccountDetailsProps) => { return ( ); -}; \ No newline at end of file +}; diff --git a/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx index b321c202445..9545f0d9a4c 100644 --- a/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/institutional-evm-account-details.tsx @@ -1,16 +1,15 @@ import React from 'react'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { InternalAccount } from '@metamask/keyring-internal-api'; -import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; type InstitutionalEVMAccountDetailsProps = { address: string; account: InternalAccount; }; -export const InstitutionalEVMAccountDetails = ({ address, account }: InstitutionalEVMAccountDetailsProps) => { - - return ( - - ); -}; \ No newline at end of file +export const InstitutionalEVMAccountDetails = ({ + address, + account, +}: InstitutionalEVMAccountDetailsProps) => { + return ; +}; diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx index 373c64d0d62..3388ca56a3c 100644 --- a/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx @@ -120,7 +120,7 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); @@ -139,7 +139,7 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); @@ -158,9 +158,7 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); @@ -176,7 +174,7 @@ describe('AccountDetails', () => { renderWithProvider( - + , store, ); diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx index f5607d36d8a..53388f202c2 100644 --- a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx @@ -34,7 +34,9 @@ export const MultichainAccountDetails = () => { return ; case 'institutional-evm': - return ; + return ( + + ); default: return null; @@ -42,4 +44,4 @@ export const MultichainAccountDetails = () => { }; return renderAccountDetailsByType(); -}; \ No newline at end of file +}; diff --git a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx index ce80a84a3ec..8fe169ac8eb 100644 --- a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { InternalAccount } from '@metamask/keyring-internal-api'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type PrivateKeyAccountDetailsProps = { @@ -8,11 +8,13 @@ type PrivateKeyAccountDetailsProps = { account: InternalAccount; }; -export const PrivateKeyAccountDetails = ({ address, account }: PrivateKeyAccountDetailsProps) => { - +export const PrivateKeyAccountDetails = ({ + address, + account, +}: PrivateKeyAccountDetailsProps) => { return ( ); -}; \ No newline at end of file +}; diff --git a/ui/pages/multichain-accounts/account-details/solana-account-details.tsx b/ui/pages/multichain-accounts/account-details/solana-account-details.tsx index 85f93dccfc2..d1e5cb4be3a 100644 --- a/ui/pages/multichain-accounts/account-details/solana-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/solana-account-details.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import { BaseAccountDetails } from '../base-account-details/base-account-details'; import { InternalAccount } from '@metamask/keyring-internal-api'; +import { BaseAccountDetails } from '../base-account-details/base-account-details'; type SolanaAccountDetailsProps = { address: string; account: InternalAccount; }; -export const SolanaAccountDetails = ({ address, account }: SolanaAccountDetailsProps) => { - - return ( - - ); -}; \ No newline at end of file +export const SolanaAccountDetails = ({ + address, + account, +}: SolanaAccountDetailsProps) => { + return ; +}; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index d7f39c76ef7..04a2d7af3cd 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -498,8 +498,6 @@ export default class Routes extends Component { ///: END:ONLY_INCLUDE_IF } = this.props; - console.log(location); - const loadMessage = loadingMessage || isNetworkLoading ? getConnectingLabel(loadingMessage, this.props, this.context) From 5206ed6d0bf28b493f101711ac639cefd9879176 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 12:11:09 -0400 Subject: [PATCH 30/55] chore: move 7702 toggles out of this PR --- .../account-details/evm-account-details.tsx | 7 +------ .../account-details/hardware-account-details.tsx | 7 +------ .../account-details/private-key-account-details.tsx | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx index 3a6e3bbdea9..dadd3ca7bca 100644 --- a/ui/pages/multichain-accounts/account-details/evm-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/evm-account-details.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { BaseAccountDetails } from '../base-account-details/base-account-details'; -import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type EVMAccountDetailsProps = { address: string; @@ -12,9 +11,5 @@ export const EVMAccountDetails = ({ address, account, }: EVMAccountDetailsProps) => { - return ( - - - - ); + return ; }; diff --git a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx index 4e91c5b557b..7aed7373388 100644 --- a/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/hardware-account-details.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { BaseAccountDetails } from '../base-account-details/base-account-details'; -import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type HardwareAccountDetailsProps = { address: string; @@ -12,9 +11,5 @@ export const HardwareAccountDetails = ({ address, account, }: HardwareAccountDetailsProps) => { - return ( - - - - ); + return ; }; diff --git a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx index 8fe169ac8eb..bb090eb3bdb 100644 --- a/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/private-key-account-details.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { BaseAccountDetails } from '../base-account-details/base-account-details'; -import { SmartContractAccountToggleSection } from '../../../components/multichain-accounts/smart-contract-account-toggle-section'; type PrivateKeyAccountDetailsProps = { address: string; @@ -12,9 +11,5 @@ export const PrivateKeyAccountDetails = ({ address, account, }: PrivateKeyAccountDetailsProps) => { - return ( - - - - ); + return ; }; From 824167f702210dea1e23de6fd97e96c3145a5acf Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 12:21:43 -0400 Subject: [PATCH 31/55] fix: apply lint fix --- ui/pages/routes/routes.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 04a2d7af3cd..10acb9c2341 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -91,6 +91,7 @@ import { import { MultichainAccountListMenu } from '../../components/multichain-accounts/multichain-account-list-menu'; import { SmartAccountUpdate } from '../confirmations/components/confirm/smart-account-update'; import { MultichainAccountDetails } from '../multichain-accounts/account-details'; +import { AddressQRCode } from '../multichain-accounts/address-qr-code'; import { getConnectingLabel, hideAppHeader, @@ -98,7 +99,6 @@ import { setTheme, showAppHeader, } from './utils'; -import { AddressQRCode } from '../multichain-accounts/address-qr-code'; // Begin Lazy Routes const OnboardingFlow = mmLazy(() => From ddd7a33521e44637eb55667f279742478ba99041 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 12:36:57 -0400 Subject: [PATCH 32/55] fix: fix type error in utils tests --- .../account-details/account-type-utils.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts index 2fe81f76f06..30bd94e7e52 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts @@ -1,3 +1,4 @@ +import { InternalAccount } from '@metamask/keyring-internal-api'; import { MOCK_ACCOUNT_EOA, MOCK_ACCOUNT_ERC4337, @@ -31,8 +32,12 @@ describe('Account Type Utils', () => { }); it('should return "unknown" for null/undefined accounts', () => { - expect(getAccountTypeCategory(null as any)).toBe('unknown'); - expect(getAccountTypeCategory(undefined as any)).toBe('unknown'); + expect(getAccountTypeCategory(null as unknown as InternalAccount)).toBe( + 'unknown', + ); + expect( + getAccountTypeCategory(undefined as unknown as InternalAccount), + ).toBe('unknown'); }); }); From 3d58992cc8d24e058c4cead3c0caf21f8ff78561 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 12:44:16 -0400 Subject: [PATCH 33/55] fix: pass correct props to base account details in its tests --- .../base-account-details.test.tsx | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.test.tsx b/ui/pages/multichain-accounts/base-account-details/base-account-details.test.tsx index f47329a481f..a6bfcacafd7 100644 --- a/ui/pages/multichain-accounts/base-account-details/base-account-details.test.tsx +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.test.tsx @@ -146,7 +146,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -175,7 +178,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -196,7 +202,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - +
Test Child Component
, @@ -215,7 +224,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -232,7 +244,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -251,7 +266,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -272,7 +290,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -296,7 +317,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -314,7 +338,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); @@ -331,7 +358,10 @@ describe('BaseAccountDetails', () => { renderWithProvider( - + , store, ); From cb82654eb99ea884c8834034b84f9380fd742d5b Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 12:55:27 -0400 Subject: [PATCH 34/55] chore: revert gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index a59d39f464b..d9b86b4a964 100644 --- a/.gitignore +++ b/.gitignore @@ -91,5 +91,3 @@ html-report-multichain/ # UI Integration tests test/integration/config/assets - -.manifest_overrides.json From 3feaf711133c3648554b3a5614955d1fce2a69bd Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Fri, 27 Jun 2025 15:18:08 -0400 Subject: [PATCH 35/55] test: add test for instiutional evm accounts --- test/data/mock-accounts.ts | 33 +++++++++++++++++++ .../account-type-utils.test.ts | 6 +++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts index 9de02cd217f..083d0349a5b 100644 --- a/test/data/mock-accounts.ts +++ b/test/data/mock-accounts.ts @@ -75,6 +75,39 @@ export const MOCK_ACCOUNT_HARDWARE: InternalAccount = { }, }; +export const MOCK_ACCOUNT_INSTITUTIONAL: InternalAccount = { + id: 'c0949edb-b843-4d20-be0b-80f2a8ef6552', + options: { + custodian: { + environmentName: 'neptune-prod', + displayName: 'Neptune', + deferPublication: false, + importOrigin: 'https://neptune-custody-ui.metamask-institutional.io', + }, + accountName: 'Custody Account A', + }, + address: '0xc073fd7d1522c4103e0d8e407fa763d3ac8417e6', + methods: [ + 'eth_signTransaction', + 'personal_sign', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + ], + type: 'eip155:eoa', + scopes: ['eip155:0'], + metadata: { + name: 'Custody Account A', + importTime: 1751048625733, + keyring: { type: KeyringTypes.snap }, + snap: { + id: 'npm:@metamask/institutional-wallet-snap', + name: 'Institutional Wallet', + enabled: true, + }, + lastSelected: 1751048625755, + }, +}; + export const MOCK_ACCOUNT_ERC4337: InternalAccount = { id: '4d5921f2-2022-44ce-a84f-9f6a0f142a5c', address: '0x123', diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts index 30bd94e7e52..5a363826d66 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.test.ts @@ -3,6 +3,7 @@ import { MOCK_ACCOUNT_EOA, MOCK_ACCOUNT_ERC4337, MOCK_ACCOUNT_HARDWARE, + MOCK_ACCOUNT_INSTITUTIONAL, MOCK_ACCOUNT_PRIVATE_KEY, MOCK_ACCOUNT_SOLANA_MAINNET, } from '../../../../test/data/mock-accounts'; @@ -98,7 +99,10 @@ describe('Account Type Utils', () => { }); describe('isInstitutionalEVMAccount', () => { - // TODO: Add tests for institutional EVM accounts + it('should return true for institutional EVM accounts', () => { + expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_INSTITUTIONAL)).toBe(true); + }); + it('should return false for regular EOA accounts', () => { expect(isInstitutionalEVMAccount(MOCK_ACCOUNT_EOA)).toBe(false); }); From 6ba18a47a954ccad418ef39cb3cfcbdde4282b25 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 18:50:11 -0400 Subject: [PATCH 36/55] chore: revert 7702 changes --- ...rt-contract-account-toggle-section.test.tsx | 5 +---- .../smart-contract-account-toggle-section.tsx | 16 ++++++++-------- .../smart-contract-account-toggle.tsx | 18 ++++++++---------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx index 4eb170a1e42..f8dee5d8783 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.test.tsx @@ -79,10 +79,7 @@ const render = (stateOverride = {}) => { ...mockState, ...stateOverride, }); - return renderWithProvider( - , - store, - ); + return renderWithProvider(, store); }; describe('SmartContractAccountToggleSection', () => { diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx index 496a63ac3f3..de3cfa81b27 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle-section/smart-contract-account-toggle-section.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; import { Box, ButtonLink, ButtonLinkSize, Text } from '../../component-library'; import { AlignItems, @@ -11,17 +13,15 @@ import { } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; +import { AppSliceState } from '../../../ducks/app/app'; import { useEIP7702Networks } from '../../../pages/confirmations/hooks/useEIP7702Networks'; import { SmartContractAccountToggle } from '../smart-contract-account-toggle'; import Preloader from '../../ui/icon/preloader'; -type SmartContractAccountToggleSectionProps = { - address: string; -}; - -export const SmartContractAccountToggleSection = ({ - address, -}: SmartContractAccountToggleSectionProps) => { +export const SmartContractAccountToggleSection = () => { + const address = useSelector( + (state: AppSliceState) => state.appState.accountDetailsAddress, + ); const t = useI18nContext(); const { network7702List, pending } = useEIP7702Networks(address); @@ -45,7 +45,7 @@ export const SmartContractAccountToggleSection = ({ ))} diff --git a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx index 816db00e34b..4f49cc44e0d 100644 --- a/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx +++ b/ui/components/multichain-accounts/smart-contract-account-toggle/smart-contract-account-toggle.tsx @@ -12,15 +12,13 @@ import { } from '../../../helpers/constants/design-system'; import { EIP7702NetworkConfiguration } from '../../../pages/confirmations/hooks/useEIP7702Networks'; -type SmartContractAccountToggleProps = { - networkConfig: EIP7702NetworkConfiguration; - address: string; -}; - export const SmartContractAccountToggle = ({ networkConfig, address, -}: SmartContractAccountToggleProps) => { +}: { + networkConfig: EIP7702NetworkConfiguration; + address: Hex; +}) => { const { name, isSupported, upgradeContractAddress, chainIdHex } = networkConfig; const { downgradeAccount, upgradeAccount, isUpgraded } = useEIP7702Account({ @@ -32,7 +30,7 @@ export const SmartContractAccountToggle = ({ const prevHasPendingRequests = useRef(); const { hasPendingRequests } = useBatchAuthorizationRequests( - address as Hex, + address, chainIdHex, ); @@ -40,7 +38,7 @@ export const SmartContractAccountToggle = ({ useEffect(() => { const checkUpgradeStatus = async () => { try { - const upgraded = await isUpgraded(address as Hex); + const upgraded = await isUpgraded(address); setAddressSupportSmartAccount(upgraded); } catch (error) { // Fall back to isSupported if we can't determine upgrade status @@ -66,9 +64,9 @@ export const SmartContractAccountToggle = ({ // Dispatch the transaction if (addressSupportSmartAccount) { - await downgradeAccount(address as Hex); + await downgradeAccount(address); } else if (upgradeContractAddress) { - await upgradeAccount(address as Hex, upgradeContractAddress); + await upgradeAccount(address, upgradeContractAddress); } }, [ address, From 40a558e85dd86502cb1722c0d1fcd7ec708a5ff4 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 18:53:07 -0400 Subject: [PATCH 37/55] feat: add push to account details route from wallet details --- .../wallet-details/wallet-details.component.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/pages/multichain-accounts/wallet-details/wallet-details.component.tsx b/ui/pages/multichain-accounts/wallet-details/wallet-details.component.tsx index 4da6f0debf4..f2a2bbf842a 100644 --- a/ui/pages/multichain-accounts/wallet-details/wallet-details.component.tsx +++ b/ui/pages/multichain-accounts/wallet-details/wallet-details.component.tsx @@ -40,7 +40,10 @@ import WalletDetailsAccountItem from '../../../components/multichain/multichain- import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display/user-preferenced-currency-display.component'; import SRPQuiz from '../../../components/app/srp-quiz-modal'; import { setAccountDetailsAddress } from '../../../store/actions'; -import { ONBOARDING_REVIEW_SRP_ROUTE } from '../../../helpers/constants/routes'; +import { + ACCOUNT_DETAILS_ROUTE, + ONBOARDING_REVIEW_SRP_ROUTE, +} from '../../../helpers/constants/routes'; type AccountBalance = { [key: string]: string | number; @@ -76,6 +79,7 @@ const WalletDetails = () => { const handleAccountClick = (account: { id: string; address: string }) => { dispatch(setAccountDetailsAddress(account.address)); + history.push(ACCOUNT_DETAILS_ROUTE); }; const handleBack = () => { From d286c9926dc42a14c7ba5ad30f19dc666c46070b Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 19:28:51 -0400 Subject: [PATCH 38/55] chore: remove unnecessary path protection --- .../address-qr-code/address-qr-code.tsx | 6 ------ .../base-account-details.tsx | 17 +++-------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx index e24256aa922..6715930b3af 100644 --- a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx @@ -75,12 +75,6 @@ export const AddressQRCode = () => { openBlockExplorer(addressLink, metricsLocation, trackEvent); }, [chainId, trackEvent, addressLink]); - useEffect(() => { - if (!address || !isMultichainAccountsState1Enabled) { - history.push(ACCOUNT_DETAILS_ROUTE); - } - }, [address, history, isMultichainAccountsState1Enabled]); - return (
{ - if (!address || !isMultichainAccountsState1Enabled) { - history.push(DEFAULT_ROUTE); - } - }, [dispatch, address, history, isMultichainAccountsState1Enabled]); - // we can never have a scenario where an account is not associated with a wallet. const { id: walletId, name: walletName } = useSelector((state) => getWalletIdAndNameByAccountAddress(state, address), From d374bcdc95f274255631b8e0802dba172b92ee75 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 19:31:19 -0400 Subject: [PATCH 39/55] chore: add param description in jsdoc --- .../account-details/account-type-utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/account-type-utils.ts b/ui/pages/multichain-accounts/account-details/account-type-utils.ts index e782e06780d..411d9a46451 100644 --- a/ui/pages/multichain-accounts/account-details/account-type-utils.ts +++ b/ui/pages/multichain-accounts/account-details/account-type-utils.ts @@ -69,7 +69,7 @@ export const getAccountTypeCategory = ( /** * Checks if an account is an EVM account (EOA or ERC-4337) * - * @param account + * @param account - The internal account object to check. */ export const isEVMAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'evm'; @@ -78,7 +78,7 @@ export const isEVMAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a Solana account * - * @param account + * @param account - The internal account object to check. */ export const isSolanaAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'solana'; @@ -87,7 +87,7 @@ export const isSolanaAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a hardware wallet account * - * @param account + * @param account - The internal account object to check. */ export const isHardwareAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'hardware'; @@ -96,7 +96,7 @@ export const isHardwareAccount = (account: InternalAccount): boolean => { /** * Checks if an account is a private key account * - * @param account + * @param account - The internal account object to check. */ export const isPrivateKeyAccount = (account: InternalAccount): boolean => { return getAccountTypeCategory(account) === 'private-key'; @@ -105,7 +105,7 @@ export const isPrivateKeyAccount = (account: InternalAccount): boolean => { /** * Checks if an account is an institutional EVM account * - * @param account + * @param account - The internal account object to check. */ export const isInstitutionalEVMAccount = ( account: InternalAccount, From 2006eb6b8365cdbf0ddeab084a481da28dbfacd2 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 19:37:50 -0400 Subject: [PATCH 40/55] chore: remove unnecessary mocking of I18nContext --- .../multichain-account-details.test.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx index 3388ca56a3c..5bc2c0f0714 100644 --- a/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.test.tsx @@ -23,11 +23,6 @@ jest.mock('react-router-dom', () => ({ }), })); -// Mock the useI18nContext hook -jest.mock('../../../hooks/useI18nContext', () => ({ - useI18nContext: () => (key: string) => key, -})); - const createMockState = (address: string, account = MOCK_ACCOUNT_EOA) => ({ appState: { accountDetailsAddress: address, @@ -167,19 +162,5 @@ describe('AccountDetails', () => { const accountNameElements = screen.getAllByText('Solana Account'); expect(accountNameElements).toHaveLength(2); }); - - it('should navigate to default route when no address is provided', () => { - const state = createMockState('', MOCK_ACCOUNT_EOA); - const store = mockStore(state); - - renderWithProvider( - - - , - store, - ); - - expect(mockPush).toHaveBeenCalledWith('/'); - }); }); }); From c9b272ac43e15c16c4d3cf047eb8dfec84a9ab3d Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 20:23:01 -0400 Subject: [PATCH 41/55] fix: lint fix --- .../multichain-accounts/address-qr-code/address-qr-code.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx index 6715930b3af..e5495a32ee8 100644 --- a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect } from 'react'; +import React, { useCallback, useContext } from 'react'; import { parseCaipChainId } from '@metamask/utils'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -33,7 +33,6 @@ import { MetaMetricsEventName, MetaMetricsEventCategory, } from '../../../../shared/constants/metametrics'; -import { getIsMultichainAccountsState1Enabled } from '../../../selectors/multichain-accounts/feature-flags'; export const AddressQRCode = () => { const t = useI18nContext(); @@ -45,9 +44,6 @@ export const AddressQRCode = () => { const account = useSelector((state) => getInternalAccountByAddress(state, address), ); - const isMultichainAccountsState1Enabled = useSelector( - getIsMultichainAccountsState1Enabled, - ); const multichainNetwork = useMultichainSelector( getMultichainNetwork, From f839d61cb28b5db40dce78dd30e210897caee036 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 20:58:12 -0400 Subject: [PATCH 42/55] fix: prevent wrapping of long values in account details row --- .../account-details-row.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ui/components/multichain-accounts/account-details-row/account-details-row.tsx b/ui/components/multichain-accounts/account-details-row/account-details-row.tsx index 01359f69624..2c0cc84d017 100644 --- a/ui/components/multichain-accounts/account-details-row/account-details-row.tsx +++ b/ui/components/multichain-accounts/account-details-row/account-details-row.tsx @@ -31,9 +31,23 @@ export const AccountDetailsRow = ({ paddingRight={4} alignItems={AlignItems.center} > - {label} - - {value} + + {label} + + + + {value} + {endAccessory} From 59c5cdbaa14068e1389de203852f01942218783f Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 30 Jun 2025 21:02:30 -0400 Subject: [PATCH 43/55] style: adjust container size --- .../account-details-row/account-details-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/multichain-accounts/account-details-row/account-details-row.tsx b/ui/components/multichain-accounts/account-details-row/account-details-row.tsx index 2c0cc84d017..fbc040c0d04 100644 --- a/ui/components/multichain-accounts/account-details-row/account-details-row.tsx +++ b/ui/components/multichain-accounts/account-details-row/account-details-row.tsx @@ -43,7 +43,7 @@ export const AccountDetailsRow = ({ color={TextColor.textAlternative} ellipsis style={{ - maxWidth: '200px', + maxWidth: '150px', }} > {value} From 02fa170c3b890b54126f73e4ff00a580eed78849 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Tue, 1 Jul 2025 08:36:10 -0400 Subject: [PATCH 44/55] style: add max width to account details --- .../base-account-details/base-account-details.scss | 3 +++ .../base-account-details/base-account-details.tsx | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 ui/pages/multichain-accounts/base-account-details/base-account-details.scss diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.scss b/ui/pages/multichain-accounts/base-account-details/base-account-details.scss new file mode 100644 index 00000000000..4a1da589757 --- /dev/null +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.scss @@ -0,0 +1,3 @@ +.multichain-account-details-page { + max-width: 600px; +} diff --git a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx index 38d50d304ad..9ee3b0841c1 100644 --- a/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx +++ b/ui/pages/multichain-accounts/base-account-details/base-account-details.tsx @@ -81,7 +81,10 @@ export const BaseAccountDetails = ({ const walletRoute = `/wallet-details/${walletId}`; return ( - +
Date: Tue, 1 Jul 2025 09:06:42 -0400 Subject: [PATCH 45/55] fix: apply code review --- .../menu-items/account-details-menu-item.js | 2 +- ui/helpers/constants/routes.ts | 5 +++-- .../multichain-account-details.tsx | 16 +++++++--------- .../address-qr-code/address-qr-code.scss | 3 +++ .../address-qr-code/address-qr-code.tsx | 16 ++++++++-------- .../base-account-details.tsx | 8 ++++---- ui/pages/pages.scss | 2 ++ 7 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 ui/pages/multichain-accounts/address-qr-code/address-qr-code.scss diff --git a/ui/components/multichain/menu-items/account-details-menu-item.js b/ui/components/multichain/menu-items/account-details-menu-item.js index 02a045a8a21..905bca975be 100644 --- a/ui/components/multichain/menu-items/account-details-menu-item.js +++ b/ui/components/multichain/menu-items/account-details-menu-item.js @@ -46,7 +46,7 @@ export const AccountDetailsMenuItem = ({ }, }); if (isMultichainAccountsState1Enabled) { - history.push(ACCOUNT_DETAILS_ROUTE); + history.push(`${ACCOUNT_DETAILS_ROUTE}/${address}`); } closeMenu?.(); }} diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index f0431aebbac..c25e3726fc9 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -126,10 +126,11 @@ PATH_NAME_MAP.set( export const NEW_ACCOUNT_ROUTE = '/new-account'; PATH_NAME_MAP.set(NEW_ACCOUNT_ROUTE, 'New Account Page'); -export const ACCOUNT_DETAILS_ROUTE = '/account-details'; +export const ACCOUNT_DETAILS_ROUTE = '/account-details/:address'; PATH_NAME_MAP.set(ACCOUNT_DETAILS_ROUTE, 'Account Details Page'); -export const ACCOUNT_DETAILS_QR_CODE_ROUTE = '/account-details/qr-code'; +export const ACCOUNT_DETAILS_QR_CODE_ROUTE = + '/account-details/qr-code/:address'; PATH_NAME_MAP.set( ACCOUNT_DETAILS_QR_CODE_ROUTE, 'Account Details QR Code Page', diff --git a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx index 53388f202c2..ac52d0cf2f0 100644 --- a/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx +++ b/ui/pages/multichain-accounts/account-details/multichain-account-details.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { AppSliceState } from '../../../ducks/app/app'; import { getInternalAccountByAddress } from '../../../selectors'; import { EVMAccountDetails } from './evm-account-details'; import { getAccountTypeCategory } from './account-type-utils'; @@ -8,11 +7,10 @@ import { SolanaAccountDetails } from './solana-account-details'; import { HardwareAccountDetails } from './hardware-account-details'; import { PrivateKeyAccountDetails } from './private-key-account-details'; import { InstitutionalEVMAccountDetails } from './institutional-evm-account-details'; +import { useParams } from 'react-router-dom'; export const MultichainAccountDetails = () => { - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); + const { address } = useParams(); const account = useSelector((state) => getInternalAccountByAddress(state, address), ); @@ -22,20 +20,20 @@ export const MultichainAccountDetails = () => { const renderAccountDetailsByType = () => { switch (accountTypeCategory) { case 'evm': - return ; + return ; case 'solana': - return ; + return ; case 'hardware': - return ; + return ; case 'private-key': - return ; + return ; case 'institutional-evm': return ( - + ); default: diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.scss b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.scss new file mode 100644 index 00000000000..c43867f4e0b --- /dev/null +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.scss @@ -0,0 +1,3 @@ +.address-qr-code-page { + max-width: 600px; +} diff --git a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx index e5495a32ee8..0a77074ff90 100644 --- a/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx +++ b/ui/pages/multichain-accounts/address-qr-code/address-qr-code.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext } from 'react'; import { parseCaipChainId } from '@metamask/utils'; import { useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import { Page, Header, @@ -22,7 +22,6 @@ import { TextVariant, } from '../../../helpers/constants/design-system'; import QrCodeView from '../../../components/ui/qr-code-view'; -import { AppSliceState } from '../../../ducks/app/app'; import { getInternalAccountByAddress } from '../../../selectors'; import { getMultichainNetwork } from '../../../selectors/multichain'; import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; @@ -37,10 +36,8 @@ import { export const AddressQRCode = () => { const t = useI18nContext(); const history = useHistory(); + const { address } = useParams(); const trackEvent = useContext(MetaMetricsContext); - const address = useSelector( - (state: AppSliceState) => state.appState.accountDetailsAddress, - ); const account = useSelector((state) => getInternalAccountByAddress(state, address), ); @@ -72,7 +69,7 @@ export const AddressQRCode = () => { }, [chainId, trackEvent, addressLink]); return ( - +
{ ariaLabel="Back" iconName={IconName.ArrowLeft} size={ButtonIconSize.Sm} - onClick={() => history.push(ACCOUNT_DETAILS_ROUTE)} + onClick={() => history.push(`${ACCOUNT_DETAILS_ROUTE}/${address}`)} /> } > {t('address')}
- +