Skip to content

Commit a7d7bb4

Browse files
authored
Merge branch 'main' into ogp/5035
2 parents 89ef315 + 058c052 commit a7d7bb4

File tree

193 files changed

+10285
-1874
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

193 files changed

+10285
-1874
lines changed

.depcheckrc.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ ignores:
5353
- 'improved-yarn-audit'
5454
- 'jetifier'
5555
- 'metro-react-native-babel-preset'
56-
- 'octonode'
5756
- 'prettier-plugin-gherkin'
5857
- 'react-native-svg-asset-plugin'
5958
- 'regenerator-runtime'

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ app/components/UI/Card/ @MetaMask/card
5353

5454
# Confirmation Team
5555
app/components/Views/confirmations @MetaMask/confirmations
56+
app/components/Views/confirmations/external/staking @MetaMask/confirmations @MetaMask/metamask-earn
5657
app/core/Engine/controllers/approval-controller @MetaMask/confirmations
5758
app/core/Engine/controllers/gas-fee-controller @MetaMask/confirmations
5859
app/core/Engine/controllers/signature-controller @MetaMask/confirmations

.storybook/storybook.requires.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- fix: Security vulnerability in RPC domain validation that could allow malicious domains to be misclassified as legitimate providers ([#17234](https://github.com/MetaMask/metamask-mobile/pull/17234))
11+
1012
## [7.50.1]
1113

1214
### Fixed

app/actions/multiSrp/index.test.ts

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from './';
99
import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english';
1010
import { createMockInternalAccount } from '../../util/test/accountsControllerTestUtils';
11+
import { TraceName, TraceOperation } from '../../util/trace';
1112
import ReduxService from '../../core/redux/ReduxService';
1213
import { RootState } from '../../reducers';
1314

@@ -28,6 +29,12 @@ const mockControllerMessenger = jest.fn();
2829
const mockAddDiscoveredAccounts = jest.fn();
2930
const mockGetAccountByAddress = jest.fn().mockReturnValue(mockExpectedAccount);
3031

32+
// Mock for seedless onboarding
33+
const mockSelectSeedlessOnboardingLoginFlow = jest.fn();
34+
const mockAddNewSeedPhraseBackup = jest.fn();
35+
const mockTrace = jest.fn();
36+
const mockEndTrace = jest.fn();
37+
3138
const hdKeyring = {
3239
getAccounts: () => {
3340
mockGetAccounts();
@@ -43,6 +50,17 @@ const mockSnapClient = {
4350
addDiscoveredAccounts: mockAddDiscoveredAccounts,
4451
};
4552

53+
jest.mock('../../selectors/seedlessOnboardingController', () => ({
54+
selectSeedlessOnboardingLoginFlow: (state: unknown) =>
55+
mockSelectSeedlessOnboardingLoginFlow(state),
56+
}));
57+
58+
jest.mock('../../util/trace', () => ({
59+
...jest.requireActual('../../util/trace'),
60+
trace: (options: unknown) => mockTrace(options),
61+
endTrace: (options: unknown) => mockEndTrace(options),
62+
}));
63+
4664
const createMockState = (hasVault: boolean) => ({
4765
engine: {
4866
backgroundState: {
@@ -81,7 +99,8 @@ jest.mock('../../core/Engine', () => ({
8199
getAccountByAddress: () => mockGetAccountByAddress(),
82100
},
83101
SeedlessOnboardingController: {
84-
addNewSeedPhraseBackup: jest.fn().mockResolvedValue(undefined),
102+
addNewSeedPhraseBackup: (seed: Uint8Array, keyringId: string) =>
103+
mockAddNewSeedPhraseBackup(seed, keyringId),
85104
},
86105
},
87106
setSelectedAddress: (address: string) => mockSetSelectedAddress(address),
@@ -103,7 +122,11 @@ describe('MultiSRP Actions', () => {
103122
describe('importNewSecretRecoveryPhrase', () => {
104123
it('imports new SRP', async () => {
105124
mockGetKeyringsByType.mockResolvedValue([]);
106-
mockAddNewKeyring.mockResolvedValue({ getAccounts: () => [testAddress] });
125+
mockAddNewKeyring.mockResolvedValue({
126+
getAccounts: () => [testAddress],
127+
id: 'keyring-id-123',
128+
});
129+
mockSelectSeedlessOnboardingLoginFlow.mockReturnValue(false);
107130

108131
await importNewSecretRecoveryPhrase(testMnemonic);
109132

@@ -131,7 +154,74 @@ describe('MultiSRP Actions', () => {
131154
expect(mockAddNewKeyring).not.toHaveBeenCalled();
132155
});
133156

157+
describe('seedless onboarding login flow', () => {
158+
beforeEach(() => {
159+
mockGetKeyringsByType.mockResolvedValue([]);
160+
mockAddNewKeyring.mockResolvedValue({
161+
getAccounts: () => [testAddress],
162+
id: 'keyring-id-123',
163+
});
164+
mockSelectSeedlessOnboardingLoginFlow.mockReturnValue(true);
165+
});
166+
167+
it('successfully adds seed phrase backup when seedless onboarding is enabled', async () => {
168+
mockAddNewSeedPhraseBackup.mockResolvedValue(undefined);
169+
170+
const result = await importNewSecretRecoveryPhrase(testMnemonic);
171+
172+
expect(mockSelectSeedlessOnboardingLoginFlow).toHaveBeenCalled();
173+
expect(mockTrace).toHaveBeenCalledWith({
174+
name: TraceName.OnboardingAddSrp,
175+
op: TraceOperation.OnboardingSecurityOp,
176+
});
177+
expect(mockAddNewSeedPhraseBackup).toHaveBeenCalledWith(
178+
expect.any(Uint8Array),
179+
'keyring-id-123',
180+
);
181+
expect(mockEndTrace).toHaveBeenCalledWith({
182+
name: TraceName.OnboardingAddSrp,
183+
data: { success: true },
184+
});
185+
expect(mockSetSelectedAddress).toHaveBeenCalledWith(testAddress);
186+
expect(result.address).toBe(testAddress);
187+
expect(mockAddDiscoveredAccounts).toHaveBeenCalled();
188+
});
189+
190+
it('handles error when seed phrase backup fails and traces error', async () => {
191+
mockAddNewSeedPhraseBackup.mockRejectedValue(
192+
new Error('Backup failed'),
193+
);
194+
195+
await expect(
196+
async () => await importNewSecretRecoveryPhrase(testMnemonic),
197+
).rejects.toThrow('Backup failed');
198+
199+
expect(mockSelectSeedlessOnboardingLoginFlow).toHaveBeenCalled();
200+
expect(mockTrace).toHaveBeenCalledWith({
201+
name: TraceName.OnboardingAddSrp,
202+
op: TraceOperation.OnboardingSecurityOp,
203+
});
204+
expect(mockAddNewSeedPhraseBackup).toHaveBeenCalledWith(
205+
expect.any(Uint8Array),
206+
'keyring-id-123',
207+
);
208+
expect(mockTrace).toHaveBeenCalledWith({
209+
name: TraceName.OnboardingAddSrpError,
210+
op: TraceOperation.OnboardingError,
211+
tags: { errorMessage: 'Backup failed' },
212+
});
213+
expect(mockEndTrace).toHaveBeenCalledWith({
214+
name: TraceName.OnboardingAddSrpError,
215+
});
216+
expect(mockEndTrace).toHaveBeenCalledWith({
217+
name: TraceName.OnboardingAddSrp,
218+
data: { success: false },
219+
});
220+
});
221+
});
222+
134223
it('calls addNewSeedPhraseBackup when seedless onboarding login flow is active', async () => {
224+
mockAddNewSeedPhraseBackup.mockResolvedValue(undefined);
135225
mockGetKeyringsByType.mockResolvedValue([]);
136226
mockAddNewKeyring.mockResolvedValue({
137227
id: 'test-keyring-id',
@@ -144,9 +234,10 @@ describe('MultiSRP Actions', () => {
144234

145235
await importNewSecretRecoveryPhrase(testMnemonic);
146236

147-
expect(
148-
Engine.context.SeedlessOnboardingController.addNewSeedPhraseBackup,
149-
).toHaveBeenCalledWith(expect.any(Uint8Array), 'test-keyring-id');
237+
expect(mockAddNewSeedPhraseBackup).toHaveBeenCalledWith(
238+
expect.any(Uint8Array),
239+
'test-keyring-id',
240+
);
150241
});
151242
});
152243

app/actions/multiSrp/index.ts

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@ import ExtendedKeyringTypes from '../../constants/keyringTypes';
44
import Engine from '../../core/Engine';
55
import { KeyringSelector } from '@metamask/keyring-controller';
66
import { InternalAccount } from '@metamask/keyring-internal-api';
7-
///: BEGIN:ONLY_INCLUDE_IF(solana)
7+
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
88
import {
99
MultichainWalletSnapFactory,
1010
WalletClientType,
1111
} from '../../core/SnapKeyring/MultichainWalletSnapClient';
1212
///: END:ONLY_INCLUDE_IF
13+
import {
14+
///: BEGIN:ONLY_INCLUDE_IF(solana)
15+
SolScope,
16+
///: END:ONLY_INCLUDE_IF
17+
///: BEGIN:ONLY_INCLUDE_IF(bitcoin)
18+
BtcScope,
19+
///: END:ONLY_INCLUDE_IF
20+
} from '@metamask/keyring-api';
1321
import {
1422
endPerformanceTrace,
1523
startPerformanceTrace,
1624
} from '../../core/redux/slices/performance';
1725
import { PerformanceEventNames } from '../../core/redux/slices/performance/constants';
1826
import { store } from '../../store';
19-
import { endTrace, trace, TraceName, TraceOperation } from '../../util/trace';
2027
import { getTraceTags } from '../../util/sentry/tags';
2128

2229
import ReduxService from '../../core/redux';
30+
import { TraceName, TraceOperation, trace, endTrace } from '../../util/trace';
2331
import { selectSeedlessOnboardingLoginFlow } from '../../selectors/seedlessOnboardingController';
2432

2533
export async function importNewSecretRecoveryPhrase(mnemonic: string) {
@@ -77,26 +85,61 @@ export async function importNewSecretRecoveryPhrase(mnemonic: string) {
7785
// on Error, wallet should notify user that the newly added seed phrase is not synced properly
7886
// user can try manual sync again (phase 2)
7987
const seed = new Uint8Array(inputCodePoints.buffer);
88+
let addSeedPhraseSuccess = false;
8089
try {
90+
trace({
91+
name: TraceName.OnboardingAddSrp,
92+
op: TraceOperation.OnboardingSecurityOp,
93+
});
8194
await SeedlessOnboardingController.addNewSeedPhraseBackup(
8295
seed,
8396
newKeyring.id,
8497
);
98+
addSeedPhraseSuccess = true;
8599
} catch (error) {
100+
const errorMessage =
101+
error instanceof Error ? error.message : 'Unknown error';
86102
// Log the error but don't let it crash the import process
87-
console.error('Failed to backup seed phrase:', error);
103+
console.error('Failed to backup seed phrase:', errorMessage);
104+
105+
trace({
106+
name: TraceName.OnboardingAddSrpError,
107+
op: TraceOperation.OnboardingError,
108+
tags: { errorMessage },
109+
});
110+
endTrace({
111+
name: TraceName.OnboardingAddSrpError,
112+
});
113+
114+
throw error;
115+
} finally {
116+
endTrace({
117+
name: TraceName.OnboardingAddSrp,
118+
data: { success: addSeedPhraseSuccess },
119+
});
88120
}
89121
}
90122

91123
let discoveredAccountsCount = 0;
92124

125+
///: BEGIN:ONLY_INCLUDE_IF(bitcoin)
126+
const bitcoinMultichainClient = MultichainWalletSnapFactory.createClient(
127+
WalletClientType.Bitcoin,
128+
);
129+
discoveredAccountsCount +=
130+
await bitcoinMultichainClient.addDiscoveredAccounts(
131+
newKeyring.id,
132+
BtcScope.Mainnet,
133+
);
134+
///: END:ONLY_INCLUDE_IF
135+
93136
///: BEGIN:ONLY_INCLUDE_IF(solana)
94-
const multichainClient = MultichainWalletSnapFactory.createClient(
137+
const solanaMultichainClient = MultichainWalletSnapFactory.createClient(
95138
WalletClientType.Solana,
96139
);
97-
98-
discoveredAccountsCount = await multichainClient.addDiscoveredAccounts(
140+
discoveredAccountsCount += await solanaMultichainClient.addDiscoveredAccounts(
99141
newKeyring.id,
142+
SolScope.Mainnet,
100143
);
101144
///: END:ONLY_INCLUDE_IF
102145

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { MultichainAddressRowProps } from './MultichainAddressRow.types';
2+
import { ProviderConfig } from '../../../../selectors/networkController';
3+
4+
export const SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS: MultichainAddressRowProps = {
5+
network: {
6+
nickname: 'Ethereum Mainnet',
7+
chainId: '0x1',
8+
ticker: 'ETH',
9+
type: 'mainnet',
10+
rpcPrefs: {},
11+
} as ProviderConfig,
12+
address: '0x1234567890123456789012345678901234567890',
13+
};
14+
15+
export const MULTICHAIN_ADDRESS_ROW_TEST_ID = 'multichain-address-row';
16+
export const MULTICHAIN_ADDRESS_ROW_NETWORK_ICON_TEST_ID =
17+
'multichain-address-row-network-icon';
18+
export const MULTICHAIN_ADDRESS_ROW_NETWORK_NAME_TEST_ID =
19+
'multichain-address-row-network-name';
20+
export const MULTICHAIN_ADDRESS_ROW_ADDRESS_TEST_ID =
21+
'multichain-address-row-address';
22+
export const MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID =
23+
'multichain-address-row-copy-button';
24+
export const MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID =
25+
'multichain-address-row-qr-button';

0 commit comments

Comments
 (0)