Skip to content

Commit 5bc7b26

Browse files
committed
Merge branch 'main' of github.com:MetaMask/metamask-extension into cleanup_incoming_txn_network_preference
2 parents c093c61 + 453944e commit 5bc7b26

File tree

211 files changed

+3650
-1509
lines changed

Some content is hidden

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

211 files changed

+3650
-1509
lines changed

.github/workflows/security-code-scanner.yml

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
name: 'MetaMask Security Code Scanner'
1+
name: MetaMask Security Code Scanner
22

33
on:
44
push:
5-
branches: ['main']
5+
branches:
6+
- main
67
pull_request:
7-
branches: ['main']
8+
branches:
9+
- main
10+
workflow_dispatch:
811

912
jobs:
1013
run-security-scan:

.storybook/index.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
margin-top: auto !important;
1010
}
1111

12-
.snap-ui-renderer__content {
13-
margin-bottom: 0 !important;
12+
.snap-ui-renderer__container {
13+
padding-bottom: 0 !important;
1414
}
1515

1616
.snap-ui-renderer__panel {

.storybook/metamask-storybook-theme.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
export const metamaskStorybookTheme = {
33
brandTitle: 'MetaMask Storybook',
44
// Typography
5-
fontBase: 'Euclid Circular B, Helvetica, Arial, sans-serif',
5+
fontBase: 'var(--font-family-sans)',
66
};

.yarn/patches/@sentry-browser-npm-8.33.1-4405cafca3.patch

+232
Large diffs are not rendered by default.

app/scripts/app-init.js

+30-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ let scriptsLoadInitiated = false;
66
const { chrome } = globalThis;
77
const testMode = process.env.IN_TEST;
88

9+
/**
10+
* @type {globalThis.stateHooks}
11+
*/
12+
globalThis.stateHooks = globalThis.stateHooks || {};
13+
914
const loadTimeLogs = [];
1015
// eslint-disable-next-line import/unambiguous
1116
function tryImport(...fileNames) {
@@ -184,12 +189,32 @@ const registerInPageContentScript = async () => {
184189
}
185190
};
186191

187-
chrome.runtime.onInstalled.addListener(function (details) {
192+
/**
193+
* `onInstalled` event handler.
194+
*
195+
* On MV3 builds we must listen for this event in `app-init`, otherwise we found that the listener
196+
* is never called.
197+
* For MV2 builds, the listener is added in `background.js` instead.
198+
*
199+
* @param {chrome.runtime.InstalledDetails} details - Event details.
200+
*/
201+
function onInstalledListener(details) {
188202
if (details.reason === 'install') {
189-
chrome.storage.session.set({ isFirstTimeInstall: true });
190-
} else if (details.reason === 'update') {
191-
chrome.storage.session.set({ isFirstTimeInstall: false });
203+
// This condition is for when `background.js` was loaded before the `onInstalled` listener was
204+
// called.
205+
if (globalThis.stateHooks.metamaskTriggerOnInstall) {
206+
globalThis.stateHooks.metamaskTriggerOnInstall();
207+
// Delete just to clean up global namespace
208+
delete globalThis.stateHooks.metamaskTriggerOnInstall;
209+
// This condition is for when the `onInstalled` listener in `app-init` was called before
210+
// `background.js` was loaded.
211+
} else {
212+
globalThis.stateHooks.metamaskWasJustInstalled = true;
213+
}
214+
chrome.runtime.onInstalled.removeListener(onInstalledListener);
192215
}
193-
});
216+
}
217+
218+
chrome.runtime.onInstalled.addListener(onInstalledListener);
194219

195220
registerInPageContentScript();

app/scripts/background.js

+35-37
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,35 @@ const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
141141
// Event emitter for state persistence
142142
export const statePersistenceEvents = new EventEmitter();
143143

144-
if (isFirefox) {
145-
browser.runtime.onInstalled.addListener(function (details) {
146-
if (details.reason === 'install') {
147-
browser.storage.session.set({ isFirstTimeInstall: true });
148-
} else if (details.reason === 'update') {
149-
browser.storage.session.set({ isFirstTimeInstall: false });
150-
}
151-
});
152-
} else if (!isManifestV3) {
153-
browser.runtime.onInstalled.addListener(function (details) {
144+
if (!isManifestV3) {
145+
/**
146+
* `onInstalled` event handler.
147+
*
148+
* On MV3 builds we must listen for this event in `app-init`, otherwise we found that the listener
149+
* is never called.
150+
* There is no `app-init` file on MV2 builds, so we add a listener here instead.
151+
*
152+
* @param {import('webextension-polyfill').Runtime.OnInstalledDetailsType} details - Event details.
153+
*/
154+
const onInstalledListener = (details) => {
154155
if (details.reason === 'install') {
155-
global.sessionStorage.setItem('isFirstTimeInstall', true);
156-
} else if (details.reason === 'update') {
157-
global.sessionStorage.setItem('isFirstTimeInstall', false);
156+
onInstall();
157+
browser.runtime.onInstalled.removeListener(onInstalledListener);
158158
}
159-
});
159+
};
160+
161+
browser.runtime.onInstalled.addListener(onInstalledListener);
162+
163+
// This condition is for when the `onInstalled` listener in `app-init` was called before
164+
// `background.js` was loaded.
165+
} else if (globalThis.stateHooks.metamaskWasJustInstalled) {
166+
onInstall();
167+
// Delete just to clean up global namespace
168+
delete globalThis.stateHooks.metamaskWasJustInstalled;
169+
// This condition is for when `background.js` was loaded before the `onInstalled` listener was
170+
// called.
171+
} else {
172+
globalThis.stateHooks.metamaskTriggerOnInstall = () => onInstall();
160173
}
161174

162175
/**
@@ -505,12 +518,6 @@ async function initialize() {
505518
await sendReadyMessageToTabs();
506519
log.info('MetaMask initialization complete.');
507520

508-
if (isManifestV3 || isFirefox) {
509-
browser.storage.session.set({ isFirstTimeInstall: false });
510-
} else {
511-
global.sessionStorage.setItem('isFirstTimeInstall', false);
512-
}
513-
514521
resolveInitialization();
515522
} catch (error) {
516523
rejectInitialization(error);
@@ -1286,24 +1293,15 @@ const addAppInstalledEvent = () => {
12861293
}, 500);
12871294
};
12881295

1289-
// On first install, open a new tab with MetaMask
1290-
async function onInstall() {
1291-
const sessionData =
1292-
isManifestV3 || isFirefox
1293-
? await browser.storage.session.get(['isFirstTimeInstall'])
1294-
: await global.sessionStorage.getItem('isFirstTimeInstall');
1295-
1296-
const isFirstTimeInstall = sessionData?.isFirstTimeInstall;
1297-
1298-
if (process.env.IN_TEST) {
1299-
addAppInstalledEvent();
1300-
} else if (!isFirstTimeInstall && !process.env.METAMASK_DEBUG) {
1301-
// If storeAlreadyExisted is true then this is a fresh installation
1302-
// and an app installed event should be tracked.
1303-
addAppInstalledEvent();
1296+
/**
1297+
* Trigger actions that should happen only upon initial install (e.g. open tab for onboarding).
1298+
*/
1299+
function onInstall() {
1300+
log.debug('First install detected');
1301+
addAppInstalledEvent();
1302+
if (!process.env.IN_TEST && !process.env.METAMASK_DEBUG) {
13041303
platform.openExtensionInBrowser();
13051304
}
1306-
onNavigateToTab();
13071305
}
13081306

13091307
function onNavigateToTab() {
@@ -1334,7 +1332,7 @@ function setupSentryGetStateGlobal(store) {
13341332
}
13351333

13361334
async function initBackground() {
1337-
await onInstall();
1335+
onNavigateToTab();
13381336
try {
13391337
await initialize();
13401338
if (process.env.IN_TEST) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { TokenRatesControllerInit } from './token-rates-controller-init';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
TokenRatesController,
3+
TokenRatesControllerMessenger,
4+
} from '@metamask/assets-controllers';
5+
import { Messenger } from '@metamask/base-controller';
6+
import { buildControllerInitRequestMock } from '../test/utils';
7+
import { ControllerInitRequest } from '../types';
8+
import { getTokenRatesControllerMessenger } from '../messengers/assets';
9+
import { TokenRatesControllerInit } from './token-rates-controller-init';
10+
11+
jest.mock('@metamask/assets-controllers');
12+
13+
function buildInitRequestMock(): jest.Mocked<
14+
ControllerInitRequest<TokenRatesControllerMessenger>
15+
> {
16+
const baseControllerMessenger = new Messenger();
17+
18+
return {
19+
...buildControllerInitRequestMock(),
20+
controllerMessenger: getTokenRatesControllerMessenger(
21+
baseControllerMessenger,
22+
),
23+
initMessenger: undefined,
24+
};
25+
}
26+
27+
describe('TokenRatesControllerInit', () => {
28+
const tokenRatesControllerClassMock = jest.mocked(TokenRatesController);
29+
30+
beforeEach(() => {
31+
jest.resetAllMocks();
32+
});
33+
34+
it('returns controller instance', () => {
35+
const requestMock = buildInitRequestMock();
36+
expect(TokenRatesControllerInit(requestMock).controller).toBeInstanceOf(
37+
TokenRatesController,
38+
);
39+
});
40+
41+
it('initializes with correct messenger and state', () => {
42+
const requestMock = buildInitRequestMock();
43+
TokenRatesControllerInit(requestMock);
44+
45+
expect(tokenRatesControllerClassMock).toHaveBeenCalled();
46+
});
47+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {
2+
CodefiTokenPricesServiceV2,
3+
TokenRatesController,
4+
} from '@metamask/assets-controllers';
5+
import { ControllerInitFunction } from '../types';
6+
import { TokenRatesControllerMessenger } from '../messengers/assets';
7+
8+
/**
9+
* Initialize the Token Rates controller.
10+
*
11+
* @param request - The request object.
12+
* @param request.controllerMessenger - The messenger to use for the controller.
13+
* @param request.persistedState - The persisted state of the extension.
14+
* @returns The initialized controller.
15+
*/
16+
export const TokenRatesControllerInit: ControllerInitFunction<
17+
TokenRatesController,
18+
TokenRatesControllerMessenger
19+
> = ({ controllerMessenger, persistedState }) => {
20+
const controller = new TokenRatesController({
21+
messenger: controllerMessenger,
22+
state: persistedState.TokenRatesController,
23+
tokenPricesService: new CodefiTokenPricesServiceV2(),
24+
disabled: !persistedState.PreferencesController?.useCurrencyRateCheck,
25+
});
26+
27+
return {
28+
controller,
29+
};
30+
};

app/scripts/controller-init/confirmations/transaction-controller-init.ts

+31-24
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,26 @@ function getControllers(
222222
};
223223
}
224224

225+
function getSmartTransactionCommonParams(flatState: ControllerFlatState) {
226+
// UI state is required to support shared selectors to avoid duplicate logic in frontend and backend.
227+
// Ideally all backend logic would instead rely on messenger event / state subscriptions.
228+
const uiState = getUIState(flatState);
229+
230+
// @ts-expect-error Smart transaction selector types does not match controller state
231+
const isSmartTransaction = getIsSmartTransaction(uiState);
232+
233+
// @ts-expect-error Smart transaction selector types does not match controller state
234+
const featureFlags = getFeatureFlagsByChainId(uiState);
235+
236+
const isHardwareWalletAccount = isHardwareWallet(uiState);
237+
238+
return {
239+
isSmartTransaction,
240+
featureFlags,
241+
isHardwareWalletAccount,
242+
};
243+
}
244+
225245
function publishSmartTransactionHook(
226246
transactionController: TransactionController,
227247
smartTransactionsController: SmartTransactionsController,
@@ -230,29 +250,22 @@ function publishSmartTransactionHook(
230250
transactionMeta: TransactionMeta,
231251
signedTransactionInHex: Hex,
232252
) {
233-
// UI state is required to support shared selectors to avoid duplicate logic in frontend and backend.
234-
// Ideally all backend logic would instead rely on messenger event / state subscriptions.
235-
const uiState = getUIState(flatState);
236-
237-
// @ts-expect-error Smart transaction selector types does not match controller state
238-
const isSmartTransaction = getIsSmartTransaction(uiState);
253+
const { isSmartTransaction, featureFlags, isHardwareWalletAccount } =
254+
getSmartTransactionCommonParams(flatState);
239255

240256
if (!isSmartTransaction) {
241257
// Will cause TransactionController to publish to the RPC provider as normal.
242258
return { transactionHash: undefined };
243259
}
244260

245-
// @ts-expect-error Smart transaction selector types does not match controller state
246-
const featureFlags = getFeatureFlagsByChainId(uiState);
247-
248261
return submitSmartTransactionHook({
249262
transactionMeta,
250263
signedTransactionInHex,
251264
transactionController,
252265
smartTransactionsController,
253266
controllerMessenger: hookControllerMessenger,
254267
isSmartTransaction,
255-
isHardwareWallet: isHardwareWallet(uiState),
268+
isHardwareWallet: isHardwareWalletAccount,
256269
// @ts-expect-error Smart transaction selector return type does not match FeatureFlags type from hook
257270
featureFlags,
258271
});
@@ -271,21 +284,16 @@ function publishBatchSmartTransactionHook({
271284
flatState: ControllerFlatState;
272285
transactions: PublishBatchHookTransaction[];
273286
}) {
274-
// UI state is required to support shared selectors to avoid duplicate logic in frontend and backend.
275-
// Ideally all backend logic would instead rely on messenger event / state subscriptions.
276-
const uiState = getUIState(flatState);
277-
278-
// @ts-expect-error Smart transaction selector types does not match controller state
279-
const isSmartTransaction = getIsSmartTransaction(uiState);
287+
const { isSmartTransaction, featureFlags, isHardwareWalletAccount } =
288+
getSmartTransactionCommonParams(flatState);
280289

281290
if (!isSmartTransaction) {
282291
// Will cause TransactionController to publish to the RPC provider as normal.
283-
return undefined;
292+
throw new Error(
293+
'publishBatchSmartTransactionHook: Smart Transaction is required for batch submissions',
294+
);
284295
}
285296

286-
// @ts-expect-error Smart transaction selector types does not match controller state
287-
const featureFlags = getFeatureFlagsByChainId(uiState);
288-
289297
// Get transactionMeta based on the last transaction ID
290298
const lastTransaction = transactions[transactions.length - 1];
291299
const transactionMeta = getTransactionById(
@@ -295,10 +303,9 @@ function publishBatchSmartTransactionHook({
295303

296304
// If we couldn't find the transaction, we should handle that gracefully
297305
if (!transactionMeta) {
298-
console.warn(
299-
`Publish batch hook: could not find transaction with id ${lastTransaction.id}`,
306+
throw new Error(
307+
`publishBatchSmartTransactionHook: Could not find transaction with id ${lastTransaction.id}`,
300308
);
301-
return undefined;
302309
}
303310

304311
return submitBatchSmartTransactionHook({
@@ -307,7 +314,7 @@ function publishBatchSmartTransactionHook({
307314
smartTransactionsController,
308315
controllerMessenger: hookControllerMessenger,
309316
isSmartTransaction,
310-
isHardwareWallet: isHardwareWallet(uiState),
317+
isHardwareWallet: isHardwareWalletAccount,
311318
// @ts-expect-error Smart transaction selector return type does not match FeatureFlags type from hook
312319
featureFlags,
313320
transactionMeta,

0 commit comments

Comments
 (0)