Skip to content

Commit 27655eb

Browse files
fix(deps): Bump @metamask/eth-json-rpc-middleware to ^14.0.0, @metamask/transaction-controller to ^35.1.1 (#26143)
## **Description** Updates `@metamask/eth-json-rpc-middleware` from `^12.1.1` to `^14.0.0`. - This version bump comes with a large number of regressions, most of them type errors. - This is because the package's dependencies are also updated by multiple major versions, and the changes include improved, stricter types (especially in `@metamask/utils`). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26143?quickstart=1) ## **Related issues** - Closes #26287 - Blocks: - MetaMask/MetaMask-planning#2991 - MetaMask/MetaMask-planning#2810 - #25733 ## Changelog ### Added - Add and export `PPOMMiddlewareRequest` type for `JsonRpcRequest` types that include the `securityAlertResponse` property. - `securityAlertResponse` is defined as both optional and nullable. - Add `PPOMRequest` type for `eth-sendTransaction` requests. ### Changed - **BREAKING:** Bump `@metamask/eth-json-rpc-middleware` from `^12.1.1` to `^14.0.0`. - **BREAKING:** Bump `@metamask/transaction-controller` from `^34.0.0` to `^35.1.1`. - **BREAKING:** Redefine `SecurityAlertsAPIRequest` as a `JsonRpcRequest` type that accepts `unknown[]` as its `params` type. - Widen the `request` parameters of the functions `validateWithController` and `validateWithAPI` to include `SecurityAlertsAPIRequest`. - Bump `@trezor/connect-web` from `9.2.2` to `9.3.0`. ### Fixed - **BREAKING:** Narrow `Params` generic parameter of `createPPOMMiddleware` function from `JsonRpcParams` to `(string | { to: string })[]`. - Add `Params` generic parameter to `handleSnapRequest` function, which defaults to `JsonRpcParams`. - `handleSnapRequest` can now be typed correctly with any `params` object. ### Security - **BREAKING:** Typed signature validation only replaces `0X` prefix with `0x`, and contract address normalization is removed for decimal and octal values. - Threat actors have been manipulating `eth_signTypedData_v4` fields to cause failures in blockaid's detectors. - Extension crashes with an error when performing Malicious permit with a non-0x prefixed integer address. - This fixes an issue where the key value row or petname component disappears if a signed address is prefixed by "0X" instead of "0x". ## **Manual testing steps** ## **Screenshots/Recordings** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.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-extension/blob/develop/.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. --------- Co-authored-by: MetaMask Bot <[email protected]>
1 parent 5f524f1 commit 27655eb

26 files changed

+2300
-1529
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
diff --git a/lib/impl/core-in-iframe.js b/lib/impl/core-in-iframe.js
2+
index c47cf3bff860d6b1855341c00b80fc6c40f9d6d5..275eb0f312ff396819fa406c154a3562842db49d 100644
3+
--- a/lib/impl/core-in-iframe.js
4+
+++ b/lib/impl/core-in-iframe.js
5+
@@ -116,7 +116,9 @@ class CoreInIframe {
6+
this._log.enabled = !!this._settings.debug;
7+
window.addEventListener('message', this.boundHandleMessage);
8+
window.addEventListener('unload', this.boundDispose);
9+
- await iframe.init(this._settings);
10+
+ const modifiedSettings = Object.assign({}, this.settings);
11+
+ modifiedSettings.env = 'webextension';
12+
+ await iframe.init(modifiedSettings);
13+
if (this._settings.sharedLogger !== false) {
14+
iframe.initIframeLogger();
15+
}
16+
diff --git a/lib/popup/index.js b/lib/popup/index.js
17+
index 9b13c370a5ac8b4e4fc0315ed40cdf615d0bb0cb..4dbd97fc28df49beb73379451974ec48a8a42ea7 100644
18+
--- a/lib/popup/index.js
19+
+++ b/lib/popup/index.js
20+
@@ -229,10 +229,12 @@ class PopupManager extends events_1.default {
21+
}
22+
else if (message.type === events_2.POPUP.LOADED) {
23+
this.handleMessage(message);
24+
+ const modifiedSettings = Object.assign({}, this.settings);
25+
+ modifiedSettings.env = 'webextension';
26+
this.channel.postMessage({
27+
type: events_2.POPUP.INIT,
28+
payload: {
29+
- settings: this.settings,
30+
+ settings: modifiedSettings,
31+
useCore: true,
32+
},
33+
});

app/scripts/lib/accounts/BalancesController.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,10 @@ export class BalancesController extends BaseController<
224224
* @param accountId - The account ID.
225225
*/
226226
#getAccount(accountId: string): InternalAccount {
227-
const account: InternalAccount = this.#listMultichainAccounts().find(
228-
(multichainAccount) => multichainAccount.id === accountId,
229-
);
227+
const account: InternalAccount | undefined =
228+
this.#listMultichainAccounts().find(
229+
(multichainAccount) => multichainAccount.id === accountId,
230+
);
230231

231232
if (!account) {
232233
throw new Error(`Unknown account: ${accountId}`);
@@ -247,13 +248,15 @@ export class BalancesController extends BaseController<
247248
const account = this.#getAccount(accountId);
248249
const partialState: BalancesControllerState = { balances: {} };
249250

250-
partialState.balances[account.id] = await this.#getBalances(
251-
account.id,
252-
account.metadata.snap.id,
253-
isBtcMainnetAddress(account.address)
254-
? BTC_MAINNET_ASSETS
255-
: BTC_TESTNET_ASSETS,
256-
);
251+
if (account.metadata.snap) {
252+
partialState.balances[account.id] = await this.#getBalances(
253+
account.id,
254+
account.metadata.snap.id,
255+
isBtcMainnetAddress(account.address)
256+
? BTC_MAINNET_ASSETS
257+
: BTC_TESTNET_ASSETS,
258+
);
259+
}
257260

258261
this.update((state: Draft<BalancesControllerState>) => ({
259262
...state,
@@ -292,7 +295,7 @@ export class BalancesController extends BaseController<
292295
return (
293296
!isEvmAccountType(account.type) &&
294297
// Non-EVM accounts are backed by a Snap for now
295-
account.metadata.snap
298+
account.metadata.snap !== undefined
296299
);
297300
}
298301

app/scripts/lib/createDupeReqFilterStream.test.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import OurReadableStream from 'readable-stream';
33
import ReadableStream2 from 'readable-stream-2';
44
import ReadableStream3 from 'readable-stream-3';
55

6-
import type { JsonRpcRequest } from '@metamask/utils';
6+
import type { JsonRpcNotification, JsonRpcRequest } from '@metamask/utils';
77
import createDupeReqFilterStream, {
88
THREE_MINUTES,
99
} from './createDupeReqFilterStream';
@@ -26,7 +26,7 @@ function createTestStream(output: JsonRpcRequest[] = [], S = Transform) {
2626
}
2727

2828
function runStreamTest(
29-
requests: JsonRpcRequest[] = [],
29+
requests: (JsonRpcRequest | JsonRpcNotification)[] = [],
3030
advanceTimersTime = 10,
3131
S = Transform,
3232
) {
@@ -54,12 +54,12 @@ describe('createDupeReqFilterStream', () => {
5454
const requests = [
5555
{ id: 1, method: 'foo' },
5656
{ id: 2, method: 'bar' },
57-
];
57+
].map((request) => ({ ...request, jsonrpc: '2.0' as const }));
5858

5959
const expectedOutput = [
6060
{ id: 1, method: 'foo' },
6161
{ id: 2, method: 'bar' },
62-
];
62+
].map((output) => ({ ...output, jsonrpc: '2.0' }));
6363

6464
const output = await runStreamTest(requests);
6565
expect(output).toEqual(expectedOutput);
@@ -69,18 +69,25 @@ describe('createDupeReqFilterStream', () => {
6969
const requests = [
7070
{ id: 1, method: 'foo' },
7171
{ id: 1, method: 'foo' }, // duplicate
72-
];
72+
].map((request) => ({ ...request, jsonrpc: '2.0' as const }));
7373

74-
const expectedOutput = [{ id: 1, method: 'foo' }];
74+
const expectedOutput = [{ id: 1, method: 'foo' }].map((output) => ({
75+
...output,
76+
jsonrpc: '2.0',
77+
}));
7578

7679
const output = await runStreamTest(requests);
7780
expect(output).toEqual(expectedOutput);
7881
});
7982

8083
it("lets through requests if they don't have an id", async () => {
81-
const requests = [{ method: 'notify1' }, { method: 'notify2' }];
84+
const requests = [{ method: 'notify1' }, { method: 'notify2' }].map(
85+
(request) => ({ ...request, jsonrpc: '2.0' as const }),
86+
);
8287

83-
const expectedOutput = [{ method: 'notify1' }, { method: 'notify2' }];
88+
const expectedOutput = [{ method: 'notify1' }, { method: 'notify2' }].map(
89+
(output) => ({ ...output, jsonrpc: '2.0' }),
90+
);
8491

8592
const output = await runStreamTest(requests);
8693
expect(output).toEqual(expectedOutput);
@@ -95,15 +102,15 @@ describe('createDupeReqFilterStream', () => {
95102
{ method: 'notify2' },
96103
{ id: 2, method: 'bar' },
97104
{ id: 3, method: 'baz' },
98-
];
105+
].map((request) => ({ ...request, jsonrpc: '2.0' as const }));
99106

100107
const expectedOutput = [
101108
{ id: 1, method: 'foo' },
102109
{ method: 'notify1' },
103110
{ id: 2, method: 'bar' },
104111
{ method: 'notify2' },
105112
{ id: 3, method: 'baz' },
106-
];
113+
].map((output) => ({ ...output, jsonrpc: '2.0' }));
107114

108115
const output = await runStreamTest(requests);
109116
expect(output).toEqual(expectedOutput);

app/scripts/lib/createDupeReqFilterStream.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export default function createDupeReqFilterStream() {
5454
transform(chunk: JsonRpcRequest, _, cb) {
5555
// JSON-RPC notifications have no ids; our only recourse is to let them through.
5656
const hasNoId = chunk.id === undefined;
57-
const requestNotYetSeen = seenRequestIds.add(chunk.id);
57+
const requestNotYetSeen =
58+
chunk.id !== null && seenRequestIds.add(chunk.id);
5859

5960
if (hasNoId || requestNotYetSeen) {
6061
cb(null, chunk);

app/scripts/lib/ppom/ppom-middleware.test.ts

+29-25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
type Hex,
3-
JsonRpcRequestStruct,
4-
JsonRpcResponseStruct,
5-
} from '@metamask/utils';
1+
import { type Hex, JsonRpcResponseStruct } from '@metamask/utils';
62
import * as ControllerUtils from '@metamask/controller-utils';
73

84
import { CHAIN_IDS } from '../../../../shared/constants/network';
@@ -12,7 +8,7 @@ import {
128
BlockaidResultType,
139
} from '../../../../shared/constants/security-provider';
1410
import { flushPromises } from '../../../../test/lib/timer-helpers';
15-
import { createPPOMMiddleware } from './ppom-middleware';
11+
import { createPPOMMiddleware, PPOMMiddlewareRequest } from './ppom-middleware';
1612
import {
1713
generateSecurityAlertId,
1814
handlePPOMError,
@@ -32,6 +28,12 @@ const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = {
3228
reason: BlockaidReason.permitFarming,
3329
};
3430

31+
const REQUEST_MOCK = {
32+
params: [],
33+
id: '',
34+
jsonrpc: '2.0' as const,
35+
};
36+
3537
const createMiddleware = (
3638
options: {
3739
chainId?: Hex;
@@ -117,14 +119,14 @@ describe('PPOMMiddleware', () => {
117119
});
118120

119121
const req = {
120-
...JsonRpcRequestStruct,
122+
...REQUEST_MOCK,
121123
method: 'eth_sendTransaction',
122124
securityAlertResponse: undefined,
123125
};
124126

125127
await middlewareFunction(
126128
req,
127-
{ ...JsonRpcResponseStruct },
129+
{ ...JsonRpcResponseStruct.TYPE },
128130
() => undefined,
129131
);
130132

@@ -141,20 +143,20 @@ describe('PPOMMiddleware', () => {
141143
it('adds loading response to confirmation requests while validation is in progress', async () => {
142144
const middlewareFunction = createMiddleware();
143145

144-
const req = {
145-
...JsonRpcRequestStruct,
146+
const req: PPOMMiddlewareRequest<(string | { to: string })[]> = {
147+
...REQUEST_MOCK,
146148
method: 'eth_sendTransaction',
147149
securityAlertResponse: undefined,
148150
};
149151

150152
await middlewareFunction(
151153
req,
152-
{ ...JsonRpcResponseStruct },
154+
{ ...JsonRpcResponseStruct.TYPE },
153155
() => undefined,
154156
);
155157

156-
expect(req.securityAlertResponse.reason).toBe(BlockaidReason.inProgress);
157-
expect(req.securityAlertResponse.result_type).toBe(
158+
expect(req.securityAlertResponse?.reason).toBe(BlockaidReason.inProgress);
159+
expect(req.securityAlertResponse?.result_type).toBe(
158160
BlockaidResultType.Loading,
159161
);
160162
});
@@ -165,11 +167,12 @@ describe('PPOMMiddleware', () => {
165167
});
166168

167169
const req = {
168-
...JsonRpcRequestStruct,
170+
...REQUEST_MOCK,
169171
method: 'eth_sendTransaction',
170172
securityAlertResponse: undefined,
171173
};
172174

175+
// @ts-expect-error Passing in invalid input for testing purposes
173176
await middlewareFunction(req, undefined, () => undefined);
174177

175178
expect(req.securityAlertResponse).toBeUndefined();
@@ -183,14 +186,14 @@ describe('PPOMMiddleware', () => {
183186
});
184187

185188
const req = {
186-
...JsonRpcRequestStruct,
189+
...REQUEST_MOCK,
187190
method: 'eth_sendTransaction',
188191
securityAlertResponse: undefined,
189192
};
190193

191194
await middlewareFunction(
192195
req,
193-
{ ...JsonRpcResponseStruct },
196+
{ ...JsonRpcResponseStruct.TYPE },
194197
() => undefined,
195198
);
196199

@@ -202,14 +205,14 @@ describe('PPOMMiddleware', () => {
202205
const middlewareFunction = createMiddleware();
203206

204207
const req = {
205-
...JsonRpcRequestStruct,
208+
...REQUEST_MOCK,
206209
method: 'eth_someRequest',
207210
securityAlertResponse: undefined,
208211
};
209212

210213
await middlewareFunction(
211214
req,
212-
{ ...JsonRpcResponseStruct },
215+
{ ...JsonRpcResponseStruct.TYPE },
213216
() => undefined,
214217
);
215218

@@ -221,15 +224,15 @@ describe('PPOMMiddleware', () => {
221224
const middlewareFunction = createMiddleware();
222225

223226
const req = {
224-
...JsonRpcRequestStruct,
227+
...REQUEST_MOCK,
225228
params: [{ to: INTERNAL_ACCOUNT_ADDRESS }],
226229
method: 'eth_sendTransaction',
227230
securityAlertResponse: undefined,
228231
};
229232

230233
await middlewareFunction(
231234
req,
232-
{ ...JsonRpcResponseStruct },
235+
{ ...JsonRpcResponseStruct.TYPE },
233236
() => undefined,
234237
);
235238

@@ -249,7 +252,7 @@ describe('PPOMMiddleware', () => {
249252
'0x935e73edb9ff52e23bac7f7e043a1ecd06d05477',
250253
'Example password',
251254
],
252-
jsonrpc: '2.0',
255+
jsonrpc: '2.0' as const,
253256
id: 2974202441,
254257
origin: 'https://metamask.github.io',
255258
networkClientId: 'mainnet',
@@ -276,6 +279,7 @@ describe('PPOMMiddleware', () => {
276279
},
277280
});
278281

282+
// @ts-expect-error Passing invalid input for testing purposes
279283
await middlewareFunction(req, undefined, () => undefined);
280284

281285
expect(req.securityAlertResponse).toBeUndefined();
@@ -287,8 +291,8 @@ describe('PPOMMiddleware', () => {
287291
const nextMock = jest.fn();
288292

289293
await middlewareFunction(
290-
{ ...JsonRpcRequestStruct, method: 'eth_sendTransaction' },
291-
{ ...JsonRpcResponseStruct },
294+
{ ...REQUEST_MOCK, method: 'eth_sendTransaction' },
295+
{ ...JsonRpcResponseStruct.TYPE },
292296
nextMock,
293297
);
294298

@@ -304,12 +308,12 @@ describe('PPOMMiddleware', () => {
304308
const middlewareFunction = createMiddleware({ error });
305309

306310
const req = {
307-
...JsonRpcRequestStruct,
311+
...REQUEST_MOCK,
308312
method: 'eth_sendTransaction',
309313
securityAlertResponse: undefined,
310314
};
311315

312-
await middlewareFunction(req, { ...JsonRpcResponseStruct }, nextMock);
316+
await middlewareFunction(req, { ...JsonRpcResponseStruct.TYPE }, nextMock);
313317

314318
expect(req.securityAlertResponse).toStrictEqual(
315319
SECURITY_ALERT_RESPONSE_MOCK,

0 commit comments

Comments
 (0)