Skip to content

Commit db9bea1

Browse files
authored
Merge branch 'main' into ogp/6257
2 parents 993e27c + de86f7d commit db9bea1

File tree

15 files changed

+407
-205
lines changed

15 files changed

+407
-205
lines changed

app/scripts/background.js

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,13 @@ import {
5555
CorruptionHandler,
5656
hasVault,
5757
} from './lib/state-corruption/state-corruption-recovery';
58+
import { initSidePanelContextMenu } from './lib/sidepanel-context-menu';
5859
import {
5960
backedUpStateKeys,
6061
PersistenceManager,
6162
} from './lib/stores/persistence-manager';
6263
import ExtensionStore from './lib/stores/extension-store';
63-
import ReadOnlyNetworkStore from './lib/stores/read-only-network-store';
64+
import { FixtureExtensionStore } from './lib/stores/fixture-extension-store';
6465
import migrations from './migrations';
6566
import Migrator from './lib/migrator';
6667
import { updateRemoteFeatureFlags } from './lib/update-remote-feature-flags';
@@ -114,10 +115,10 @@ const BADGE_COLOR_NOTIFICATION = '#D73847';
114115
const BADGE_MAX_COUNT = 9;
115116

116117
const inTest = process.env.IN_TEST;
117-
const useReadOnlyNetworkStore =
118+
const useFixtureStore =
118119
inTest && getManifestFlags().testing?.forceExtensionStore !== true;
119-
const localStore = useReadOnlyNetworkStore
120-
? new ReadOnlyNetworkStore()
120+
const localStore = useFixtureStore
121+
? new FixtureExtensionStore()
121122
: new ExtensionStore();
122123
const persistenceManager = new PersistenceManager({ localStore });
123124

@@ -180,7 +181,10 @@ const ONE_SECOND_IN_MILLISECONDS = 1_000;
180181
// Timeout for initializing phishing warning page.
181182
const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
182183

183-
lazyListener.once('runtime', 'onInstalled').then(handleOnInstalled);
184+
lazyListener.once('runtime', 'onInstalled').then((details) => {
185+
handleOnInstalled(details);
186+
handleSidePanelContextMenu();
187+
});
184188

185189
/**
186190
* This deferred Promise is used to track whether initialization has finished.
@@ -1657,26 +1661,14 @@ function onInstall() {
16571661
platform.openExtensionInBrowser();
16581662
}
16591663
}
1660-
// Only register sidepanel context menu for browsers that support it (Chrome/Edge/Brave)
1661-
// and when the feature flag is enabled
1662-
if (
1663-
browser.contextMenus &&
1664-
browser.sidePanel &&
1665-
process.env.IS_SIDEPANEL?.toString() === 'true'
1666-
) {
1667-
browser.runtime.onInstalled.addListener(() => {
1668-
browser.contextMenus.create({
1669-
id: 'openSidePanel',
1670-
title: 'MetaMask Sidepanel',
1671-
contexts: ['all'],
1672-
});
1673-
});
1674-
browser.contextMenus.onClicked.addListener((info, tab) => {
1675-
if (info.menuItemId === 'openSidePanel') {
1676-
// This will open the panel in all the pages on the current window.
1677-
browser.sidePanel.open({ windowId: tab.windowId });
1678-
}
1679-
});
1664+
1665+
/**
1666+
* Handles the onInstalled event for sidepanel context menu creation.
1667+
* This is registered via lazyListener to catch the event at module load time.
1668+
*/
1669+
async function handleSidePanelContextMenu() {
1670+
await isInitialized;
1671+
await initSidePanelContextMenu(controller);
16801672
}
16811673

16821674
/**

app/scripts/lib/dapp-swap/dapp-swap-middleware.test.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@ const fetchQuotes = jest.fn();
1919
const setSwapQuotes = jest.fn();
2020
const getNetworkConfigurationByNetworkClientId = jest.fn();
2121

22-
const createMiddleware = () => {
22+
const createMiddleware = (
23+
args: {
24+
// eslint-disable-next-line @typescript-eslint/naming-convention
25+
dappSwapMetricsFlag?: { enabled: boolean; bridge_quote_fees: number };
26+
} = {},
27+
) => {
2328
const middlewareFunction = createDappSwapMiddleware({
2429
fetchQuotes,
2530
setSwapQuotes,
2631
getNetworkConfigurationByNetworkClientId,
32+
// eslint-disable-next-line @typescript-eslint/naming-convention
33+
dappSwapMetricsFlag: { enabled: true, bridge_quote_fees: 250 },
34+
...args,
2735
});
2836
return { middlewareFunction };
2937
};
@@ -50,6 +58,42 @@ describe('DappSwapMiddleware', () => {
5058
await flushPromises();
5159
});
5260

61+
it('does not fetch quotes if dapp swap is not enabled', async () => {
62+
fetchQuotes.mockReturnValueOnce(mockBridgeQuotes);
63+
const { middlewareFunction } = createMiddleware({
64+
// eslint-disable-next-line @typescript-eslint/naming-convention
65+
dappSwapMetricsFlag: { enabled: false, bridge_quote_fees: 250 },
66+
});
67+
68+
const req = {
69+
...REQUEST_MOCK,
70+
method: 'eth_sendTransaction',
71+
origin: 'https://metamask.github.io',
72+
securityAlertResponse: {
73+
securityAlertId: '123',
74+
},
75+
params: [
76+
{
77+
data: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000068f0dd1b0000000000000000000000000000000000000000000000000000000000000003100604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003070b0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000007ffc3dbf3b2b50ff3a1d5523bc24bb5043837b1400000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000178239802520a9c99dcbd791f81326b70298d62900000000000000000000000000000000000000000000000000000000000601470c',
78+
from: '0x12312312312312',
79+
chainId: '1',
80+
calls: [],
81+
},
82+
],
83+
networkClientId: 'networkClientId',
84+
};
85+
86+
await middlewareFunction(
87+
req as unknown as DappSwapMiddlewareRequest<(string | { to: string })[]>,
88+
{ ...JsonRpcResponseStruct.TYPE },
89+
() => undefined,
90+
);
91+
92+
await flushPromises();
93+
94+
expect(fetchQuotes).not.toHaveBeenCalled();
95+
});
96+
5397
it('for correct origin, fetches quotes and sets swap quotes', async () => {
5498
fetchQuotes.mockReturnValueOnce(mockBridgeQuotes);
5599
getNetworkConfigurationByNetworkClientId.mockReturnValueOnce({
@@ -67,7 +111,7 @@ describe('DappSwapMiddleware', () => {
67111
},
68112
params: [
69113
{
70-
data: '0x123123123',
114+
data: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000068f0dd1b0000000000000000000000000000000000000000000000000000000000000003100604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003070b0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000007ffc3dbf3b2b50ff3a1d5523bc24bb5043837b1400000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000178239802520a9c99dcbd791f81326b70298d62900000000000000000000000000000000000000000000000000000000000601470c',
71115
from: '0x12312312312312',
72116
calls: [],
73117
},
@@ -84,8 +128,15 @@ describe('DappSwapMiddleware', () => {
84128
await flushPromises();
85129

86130
expect(fetchQuotes).toHaveBeenCalledWith({
87-
walletAddress: '0x12312312312312',
131+
destChainId: '0x1',
132+
destTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
88133
fee: 250,
134+
gasIncluded: false,
135+
gasIncluded7702: false,
136+
srcChainId: '0x1',
137+
srcTokenAddress: '0x0000000000000000000000000000000000000000',
138+
srcTokenAmount: '0x5af3107a4000',
139+
walletAddress: '0x12312312312312',
89140
});
90141
});
91142
});

app/scripts/lib/dapp-swap/dapp-swap-middleware.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type DappSwapMiddlewareRequest<
3535
const FOUR_BYTE_EXECUTE_SWAP_CONTRACT = '0x3593564c';
3636
const DAPP_SWAP_COMPARISON_ORIGIN = 'https://app.uniswap.org';
3737
const TEST_DAPP_ORIGIN = 'https://metamask.github.io';
38+
const DEFAULT_QUOTEFEE = 250;
3839

3940
const getSwapDetails = (params: DappSwapMiddlewareRequest['params']) => {
4041
if (!params?.length) {
@@ -67,6 +68,7 @@ export function createDappSwapMiddleware<
6768
fetchQuotes,
6869
setSwapQuotes,
6970
getNetworkConfigurationByNetworkClientId,
71+
dappSwapMetricsFlag,
7072
}: {
7173
fetchQuotes: (quotesInput: GenericQuoteRequest) => Promise<QuoteResponse[]>;
7274
setSwapQuotes: (
@@ -76,13 +78,22 @@ export function createDappSwapMiddleware<
7678
getNetworkConfigurationByNetworkClientId: (
7779
networkClientId: NetworkClientId,
7880
) => NetworkConfiguration | undefined;
81+
// eslint-disable-next-line @typescript-eslint/naming-convention
82+
dappSwapMetricsFlag: { enabled: boolean; bridge_quote_fees: number };
7983
}) {
8084
return async (
8185
req: DappSwapMiddlewareRequest<Params>,
8286
_res: JsonRpcResponse<Result>,
8387
next: () => void,
8488
) => {
8589
try {
90+
// eslint-disable-next-line @typescript-eslint/naming-convention
91+
const { enabled: dappSwapEnabled, bridge_quote_fees: bridgeQuoteFees } =
92+
dappSwapMetricsFlag;
93+
if (!dappSwapEnabled) {
94+
next();
95+
return;
96+
}
8697
const { securityAlertResponse, params, origin } = req;
8798
const { securityAlertId } = securityAlertResponse ?? {};
8899

@@ -96,10 +107,14 @@ export function createDappSwapMiddleware<
96107

97108
const { data, from } = getSwapDetails(params);
98109
if (data && securityAlertId && chainId) {
99-
const { quotesInput } = getDataFromSwap(chainId as Hex, data, from);
110+
const { quotesInput } = getDataFromSwap(chainId as Hex, data);
100111
if (quotesInput) {
101112
const startTime = new Date().getTime();
102-
fetchQuotes(quotesInput)
113+
fetchQuotes({
114+
...quotesInput,
115+
walletAddress: from,
116+
fee: bridgeQuoteFees ?? DEFAULT_QUOTEFEE,
117+
})
103118
.then((quotes) => {
104119
const endTime = new Date().getTime();
105120
const latency = endTime - startTime;

app/scripts/lib/setup-initial-state-hooks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { maskObject } from '../../../shared/modules/object.utils';
22
import ExtensionPlatform from '../platforms/extension';
33
import { SENTRY_BACKGROUND_STATE } from '../constants/sentry-state';
4-
import ReadOnlyNetworkStore from './stores/read-only-network-store';
4+
import { FixtureExtensionStore } from './stores/fixture-extension-store';
55
import ExtensionStore from './stores/extension-store';
66
import { PersistenceManager } from './stores/persistence-manager';
77

@@ -10,7 +10,7 @@ const platform = new ExtensionPlatform();
1010
// This instance of `localStore` is used by Sentry to get the persisted state
1111
const sentryLocalStore = new PersistenceManager({
1212
localStore: process.env.IN_TEST
13-
? new ReadOnlyNetworkStore()
13+
? new FixtureExtensionStore()
1414
: new ExtensionStore(),
1515
});
1616

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import browser from 'webextension-polyfill';
2+
import type { RemoteFeatureFlagControllerState } from '@metamask/remote-feature-flag-controller';
3+
import type MetamaskController from '../metamask-controller';
4+
5+
const MENU_ITEM_ID = 'openSidePanel';
6+
7+
// Type augmentation for sidePanel API (not yet in webextension-polyfill types)
8+
type BrowserWithSidePanel = typeof browser & {
9+
sidePanel?: {
10+
open: (options: { windowId: number }) => Promise<void>;
11+
};
12+
};
13+
14+
export async function initSidePanelContextMenu(
15+
controller: MetamaskController,
16+
): Promise<void> {
17+
const browserWithSidePanel = browser as BrowserWithSidePanel;
18+
19+
if (
20+
!browser.contextMenus ||
21+
!browserWithSidePanel.sidePanel ||
22+
process.env.IS_SIDEPANEL?.toString() !== 'true'
23+
) {
24+
return;
25+
}
26+
27+
const isEnabled = (state?: {
28+
remoteFeatureFlags?: { extensionUxSidepanel?: boolean };
29+
}) => state?.remoteFeatureFlags?.extensionUxSidepanel !== false;
30+
31+
const createMenu = () => {
32+
browser.contextMenus.create({
33+
id: MENU_ITEM_ID,
34+
title: 'MetaMask Sidepanel',
35+
contexts: ['all'],
36+
});
37+
};
38+
39+
const removeMenu = () => {
40+
browser.contextMenus.remove(MENU_ITEM_ID);
41+
};
42+
43+
if (isEnabled(controller?.remoteFeatureFlagController?.state)) {
44+
createMenu();
45+
}
46+
47+
browser.contextMenus.onClicked.addListener((info, tab) => {
48+
if (info.menuItemId === MENU_ITEM_ID && tab?.windowId) {
49+
browserWithSidePanel.sidePanel?.open({ windowId: tab.windowId });
50+
}
51+
});
52+
53+
controller?.controllerMessenger?.subscribe(
54+
'RemoteFeatureFlagController:stateChange',
55+
(state: RemoteFeatureFlagControllerState) => {
56+
if (isEnabled(state)) {
57+
createMenu();
58+
} else {
59+
removeMenu();
60+
}
61+
},
62+
);
63+
}

0 commit comments

Comments
 (0)