Skip to content

Commit ca72707

Browse files
authored
feat: add srp pills (#14829)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR adds new SRP labels when there are multiple srps, or when an account derives entropy from a srp. Changes: 1. Updated tests to mock keyring metadata 2. Updated `getLabelTextByAddress` in `app/util/address/index.ts` to check for multiple srps when its an hd account and entropySource for snap accounts. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 3. What is the improvement/solution? --> ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMMULTISRP-186?atlOrigin=eyJpIjoiZDg5NTZhMmEzYjgwNGNiYmIyNDE2NGU5ZjMxNTg0NzAiLCJwIjoiaiJ9 ## **Manual testing steps** 1. Import a new srp 2. Click on account selector 4. See that there are two accounts with the SRP # label. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ![Screenshot 2025-04-24 at 20 51 04](https://github.com/user-attachments/assets/00b37a22-671b-4f09-9383-8f7c72b5ce8c) ### **After** ![Screenshot 2025-04-24 at 20 50 10](https://github.com/user-attachments/assets/1dcd7df9-dd52-4af6-be6f-e70b2f67e822) <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 4bd6f1e commit ca72707

19 files changed

Lines changed: 335 additions & 102 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- feat(bridge): use `BridgeStatusController` for EVM and Solana Bridge transaction submission ([#14708](https://github.com/MetaMask/metamask-mobile/pull/14708))
2727
- feat(multi-srp): add discover accounts to MultichainWalletSnapClient ([#14727](https://github.com/MetaMask/metamask-mobile/pull/14727))
2828
- feat: real time dapp scanning BrowserTab ([#14515](https://github.com/MetaMask/metamask-mobile/pull/14515))
29+
- feat(multi-srp): add new srp pills labels ([#14829](https://github.com/MetaMask/metamask-mobile/pull/14829))
2930
- feat(earn): add pooled-staking and stablecoin lending remote feature flags ([#14660](https://github.com/MetaMask/metamask-mobile/pull/14660))
3031
- feat: feat: AccountConnect and AccountApproval use dapp scanning ([#14514](https://github.com/MetaMask/metamask-mobile/pull/14514/))
3132

app/components/UI/AccountApproval/index.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ jest.mock('../../../core/Engine', () => {
3333
accounts: ['0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756'],
3434
},
3535
],
36+
keyringsMetadata: [
37+
{
38+
id: '01JNG71B7GTWH0J1TSJY9891S0',
39+
name: '',
40+
},
41+
],
3642
},
3743
},
3844
AccountsController: {

app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { createMockAccountsControllerState } from '../../../util/test/accountsCo
1414
import { RootState } from '../../../reducers';
1515
import { AssetsContractController } from '@metamask/assets-controllers';
1616
import { CHAIN_IDS } from '@metamask/transaction-controller';
17+
import { MOCK_KEYRING_CONTROLLER_STATE } from '../../../util/test/keyringControllerTestUtils';
1718

1819
const MOCK_ADDRESS_1 = '0xe64dD0AB5ad7e8C5F2bf6Ce75C34e187af8b920A';
1920
const MOCK_ADDRESS_2 = '0x519d2CE57898513F676a5C3b66496c3C394c9CC7';
@@ -50,6 +51,11 @@ const mockInitialState: DeepPartial<RootState> = {
5051
},
5152
},
5253
AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
54+
KeyringController: {
55+
vault: 'mock-vault',
56+
isUnlocked: true,
57+
...MOCK_KEYRING_CONTROLLER_STATE,
58+
},
5359
},
5460
},
5561
};
@@ -63,6 +69,8 @@ const mockGetERC20BalanceOf = jest.fn().mockReturnValue(0x0186a0);
6369
jest.mock('../../../core/Engine', () => {
6470
const { MOCK_ACCOUNTS_CONTROLLER_STATE: mockAccountsControllerState } =
6571
jest.requireActual('../../../util/test/accountsControllerTestUtils');
72+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
73+
6674
return {
6775
context: {
6876
TokensController: {
@@ -72,13 +80,20 @@ jest.mock('../../../core/Engine', () => {
7280
state: {
7381
keyrings: [
7482
{
83+
type: KeyringTypes.hd,
7584
accounts: [
7685
'0xe64dD0AB5ad7e8C5F2bf6Ce75C34e187af8b920A',
7786
'0x519d2CE57898513F676a5C3b66496c3C394c9CC7',
7887
'0x07Be9763a718C0539017E2Ab6fC42853b4aEeb6B',
7988
],
8089
},
8190
],
91+
keyringsMetadata: [
92+
{
93+
id: '01JNG71B7GTWH0J1TSJY9891S0',
94+
name: '',
95+
},
96+
],
8297
},
8398
},
8499
AccountsController: {

app/components/UI/AccountOverview/index.test.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const mockedEngine = Engine;
1414
jest.mock('../../../core/Engine', () => {
1515
const { MOCK_ACCOUNTS_CONTROLLER_STATE: mockAccountsControllerState } =
1616
jest.requireActual('../../../util/test/accountsControllerTestUtils');
17+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
18+
1719
return {
1820
init: () => mockedEngine.init({}),
1921
context: {
@@ -24,7 +26,13 @@ jest.mock('../../../core/Engine', () => {
2426
{
2527
accounts: ['0xC4955C0d639D99699Bfd7Ec54d9FaFEe40e4D272'],
2628
index: 0,
27-
type: 'HD Key Tree',
29+
type: KeyringTypes.hd,
30+
},
31+
],
32+
keyringsMetadata: [
33+
{
34+
id: '01JNG71B7GTWH0J1TSJY9891S0',
35+
name: '',
2836
},
2937
],
3038
},

app/components/UI/Bridge/components/DestinationAccountSelector.tsx/DestinationAccountSelector.test.tsx

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,48 @@ import DestinationAccountSelector from './index';
77
import { backgroundState } from '../../../../../util/test/initial-root-state';
88

99
// Mock Engine
10-
jest.mock('../../../../../core/Engine', () => ({
11-
context: {
12-
AccountsController: {
13-
state: {
14-
internalAccounts: {
15-
accounts: {
16-
'4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi': {
17-
address: '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
18-
name: 'Account 1',
19-
},
20-
'5vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi': {
21-
address: '5vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
22-
name: 'Account 2',
10+
jest.mock('../../../../../core/Engine', () => {
11+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
12+
return {
13+
context: {
14+
AccountsController: {
15+
state: {
16+
internalAccounts: {
17+
accounts: {
18+
'4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi': {
19+
address: '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
20+
name: 'Account 1',
21+
},
22+
'5vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi': {
23+
address: '5vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
24+
name: 'Account 2',
25+
},
2326
},
2427
},
2528
},
2629
},
30+
KeyringController: {
31+
state: {
32+
keyrings: [
33+
{
34+
type: KeyringTypes.hd,
35+
accounts: [
36+
'4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
37+
'5vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
38+
],
39+
},
40+
],
41+
keyringsMetadata: [
42+
{
43+
id: '01JNG71B7GTWH0J1TSJY9891S0',
44+
name: '',
45+
},
46+
],
47+
},
48+
},
2749
},
28-
},
29-
}));
50+
};
51+
});
3052

3153
// Mock React Native Linking
3254
jest.mock('react-native/Libraries/Linking/Linking', () => ({
@@ -140,7 +162,9 @@ describe('DestinationAccountSelector', () => {
140162
it('clears destination address when close button is pressed', () => {
141163
const { getByTestId, store } = renderComponent();
142164
// The close button is a ButtonIcon component with IconName.Close
143-
const closeButton = getByTestId('cellselect').findByProps({ iconName: 'Close' });
165+
const closeButton = getByTestId('cellselect').findByProps({
166+
iconName: 'Close',
167+
});
144168
fireEvent.press(closeButton);
145169

146170
const actions = store.getActions();
@@ -186,7 +210,9 @@ describe('DestinationAccountSelector', () => {
186210

187211
it('clears destination when close button is pressed', () => {
188212
const { getByTestId, store } = renderComponent();
189-
const closeButton = getByTestId('cellselect').findByProps({ iconName: 'Close' });
213+
const closeButton = getByTestId('cellselect').findByProps({
214+
iconName: 'Close',
215+
});
190216
fireEvent.press(closeButton);
191217

192218
const actions = store.getActions();

app/components/UI/WalletAccount/WalletAccount.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ const mockAccount: Account = {
3939
jest.mock('../../../core/Engine', () => {
4040
const { MOCK_ACCOUNTS_CONTROLLER_STATE: mockAccountsControllerState } =
4141
jest.requireActual('../../../util/test/accountsControllerTestUtils');
42+
const { MOCK_KEYRING_CONTROLLER_STATE: mockKeyringControllerState } =
43+
jest.requireActual('../../../util/test/keyringControllerTestUtils');
4244
return {
4345
context: {
4446
AccountsController: {
4547
...mockAccountsControllerState,
4648
state: mockAccountsControllerState,
4749
},
50+
KeyringController: { state: mockKeyringControllerState },
4851
},
4952
};
5053
});

app/components/Views/AccountConnect/AccountConnect.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ jest.mock('../../../core/Engine', () => {
7070
[MOCK_ADDRESS_1, MOCK_ADDRESS_2],
7171
MOCK_ADDRESS_1,
7272
);
73+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
7374

7475
return {
7576
context: {
@@ -92,6 +93,22 @@ jest.mock('../../../core/Engine', () => {
9293
AccountsController: {
9394
state: mockAccountsState,
9495
},
96+
KeyringController: {
97+
state: {
98+
keyrings: [
99+
{
100+
type: KeyringTypes.hd,
101+
accounts: [MOCK_ADDRESS_1, MOCK_ADDRESS_2],
102+
},
103+
],
104+
keyringsMetadata: [
105+
{
106+
id: '01JNG71B7GTWH0J1TSJY9891S0',
107+
name: '',
108+
},
109+
],
110+
},
111+
},
95112
},
96113
};
97114
});

app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.test.tsx

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,51 @@ jest.mock('@react-navigation/native', () => ({
2323
}),
2424
}));
2525

26-
jest.mock('../../../../core/Engine', () => ({
27-
context: {
28-
PermissionController: {
29-
revokeAllPermissions: jest.fn(),
30-
},
31-
AccountsController: {
32-
state: {
33-
internalAccounts: {
34-
accounts: {
35-
'0x1234': {
36-
address: '0x1234',
37-
name: 'Account 1',
38-
type: 'simple',
39-
},
40-
'0x5678': {
41-
address: '0x5678',
42-
name: 'Account 2',
43-
type: 'simple',
26+
jest.mock('../../../../core/Engine', () => {
27+
// eslint-disable-next-line @typescript-eslint/no-shadow
28+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
29+
return {
30+
context: {
31+
PermissionController: {
32+
revokeAllPermissions: jest.fn(),
33+
},
34+
AccountsController: {
35+
state: {
36+
internalAccounts: {
37+
accounts: {
38+
'0x1234': {
39+
address: '0x1234',
40+
name: 'Account 1',
41+
type: 'simple',
42+
},
43+
'0x5678': {
44+
address: '0x5678',
45+
name: 'Account 2',
46+
type: 'simple',
47+
},
4448
},
4549
},
4650
},
4751
},
52+
KeyringController: {
53+
state: {
54+
keyrings: [
55+
{
56+
type: KeyringTypes.hd,
57+
accounts: ['0x1234', '0x5678'],
58+
},
59+
],
60+
keyringsMetadata: [
61+
{
62+
id: '01JNG71B7GTWH0J1TSJY9891S0',
63+
name: '',
64+
},
65+
],
66+
},
67+
},
4868
},
49-
},
50-
}));
69+
};
70+
});
5171

5272
const mockAccounts = [
5373
{

app/components/Views/Wallet/index.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jest.mock('../../../util/address', () => {
2323
jest.mock('../../../core/Engine', () => {
2424
const { MOCK_ACCOUNTS_CONTROLLER_STATE: mockAccountsControllerState } =
2525
jest.requireActual('../../../util/test/accountsControllerTestUtils');
26+
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');
27+
2628
return {
2729
getTotalEvmFiatAccountBalance: jest.fn().mockReturnValue({
2830
totalNativeTokenBalance: { amount: '1', unit: 'ETH' },
@@ -61,6 +63,13 @@ jest.mock('../../../core/Engine', () => {
6163
keyrings: [
6264
{
6365
accounts: ['0xC4955C0d639D99699Bfd7Ec54d9FaFEe40e4D272'],
66+
type: KeyringTypes.hd,
67+
},
68+
],
69+
keyringsMetadata: [
70+
{
71+
id: '01JNG71B7GTWH0J1TSJY9891S0',
72+
name: '',
6473
},
6574
],
6675
},

0 commit comments

Comments
 (0)