From af3b5a9d9dcd3b59e92fb66d592969089ff66763 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 4 Sep 2026 21:49:40 +0200 Subject: [PATCH 1/5] feat!: drop support for Node 18 and 20 Mirrors MetaMask/core#9976, the bottom of core's foundational stack. BREAKING: the supported range moves from ^18.18 || ^20.14 || >=22 to ^22.14.0 || ^24, the same range core adopts. The CI matrix drops to 22 and 24, and constraints.pro is updated to match. This lands before the TypeScript and target/lib changes on purpose. Core sequenced its stack Node, then ESM, then TypeScript, then target/lib, and that order is a dependency chain rather than an arbitrary grouping: raising the runtime floor first is what justifies each step above it, and emitting a higher target is only defensible once the floor guarantees it. --- .github/workflows/build-lint-test.yml | 6 +++--- CHANGELOG.md | 2 ++ constraints.pro | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 6ff43e28e..1bd0e9d3c 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [22.x, 24.x] steps: - name: Checkout and setup environment uses: MetaMask/action-checkout-and-setup@v1 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [22.x, 24.x] steps: - name: Checkout and setup environment uses: MetaMask/action-checkout-and-setup@v1 @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [22.x, 24.x] steps: - name: Checkout and setup environment uses: MetaMask/action-checkout-and-setup@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6817943e3..6f989b915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** Drop support for Node 18 and 20 ([#328](https://github.com/MetaMask/utils/pull/328)) + - The supported range is now `^22.14.0 || ^24`, matching core. - Bump `@ethereumjs/tx` from `^4.2.0` to `^5.4.0` ([#321](https://github.com/MetaMask/utils/pull/321)) - The deprecated `Keyring.signTransaction` now returns `LegacyTxData` rather than `TxData`. These describe the same shape: `@ethereumjs/tx@5` repurposed the name `TxData` for a map keyed by transaction type and renamed the old meaning to `LegacyTxData`. Implementations do not need changing. - Bump `@metamask/scure-bip39` from `^2.0.3` to `^2.1.1` ([#311](https://github.com/MetaMask/utils/pull/311)) diff --git a/constraints.pro b/constraints.pro index 0bec58439..091746827 100644 --- a/constraints.pro +++ b/constraints.pro @@ -92,7 +92,7 @@ gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, DependencyType) :- DependencyType == 'devDependencies'. % The package must specify the expected minimum Node version. -gen_enforced_field(WorkspaceCwd, 'engines.node', '^18.18 || ^20.14 || >=22'). +gen_enforced_field(WorkspaceCwd, 'engines.node', '^22.14.0 || ^24'). % The package is public. gen_enforced_field(WorkspaceCwd, 'publishConfig.access', 'public'). diff --git a/package.json b/package.json index 6dce1a8f1..64a493698 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "directory": "src" }, "engines": { - "node": "^18.18 || ^20.14 || >=22" + "node": "^22.14.0 || ^24" }, "packageManager": "yarn@4.16.0", "lavamoat": { From 28cc8cfe1a87a381d6e4d4747248f38e1723741c Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Sat, 5 Sep 2026 12:43:26 +0200 Subject: [PATCH 2/5] chore: bump @types/node to ^22.13.14 Follows from the runtime floor moving to Node 22. Core makes the same bump in the same PR as its Node drop (MetaMask/core#9976), rather than alongside the TypeScript change, since the types track the supported runtime. --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 64a493698..8e919e35d 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@ts-bridge/shims": "^0.1.1", "@types/jest": "^30.0.0", "@types/jest-when": "^3.5.3", - "@types/node": "~18.18.14", + "@types/node": "^22.13.14", "@types/semver": "^7", "@types/uuid": "^9.0.8", "eslint": "^9.39.1", diff --git a/yarn.lock b/yarn.lock index 59d8a4107..e650b41d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1212,7 +1212,7 @@ __metadata: "@types/jest": "npm:^30.0.0" "@types/jest-when": "npm:^3.5.3" "@types/lodash": "npm:^4.17.20" - "@types/node": "npm:~18.18.14" + "@types/node": "npm:^22.13.14" "@types/semver": "npm:^7" "@types/uuid": "npm:^9.0.8" debug: "npm:^4.3.4" @@ -2362,12 +2362,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:~18.18.14": - version: 18.18.14 - resolution: "@types/node@npm:18.18.14" +"@types/node@npm:*, @types/node@npm:^22.13.14": + version: 22.20.1 + resolution: "@types/node@npm:22.20.1" dependencies: - undici-types: "npm:~5.26.4" - checksum: 10/508e90549c2893ec3b8e5f133763a3983adf37b6c8cfb3f1ea066f63d45f57a7246cf6c23aa859417d41d5c0b3f76883d66a749d2f1aa1ca486a47231286a01f + undici-types: "npm:~6.21.0" + checksum: 10/0949e49d0383569ebd1222a2f65a9adb9328a6a6dbed459a02cdb37c61147755386c004c4ffbe436de59f5859df1774b1dd6bf168f7aeee46884bb190e0c384f languageName: node linkType: hard @@ -7769,10 +7769,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10/0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard From 67ad4d3ab1d4488462647aafc791f61baaebafd7 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 9 Sep 2026 21:00:02 +0200 Subject: [PATCH 3/5] test: drop the Node 18 crypto polyfill, now unreachable --- eslint.config.mjs | 6 +++--- src/hashing.test.ts | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 31fd73988..3c6d970a8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -82,9 +82,9 @@ const config = createConfig([ { files: ['**/*.test.{js,ts}'], rules: { - // These tests deliberately reach for `crypto` and `crypto.webcrypto`, - // and polyfill the global when running on Node 18. Flagging them as - // unsupported defeats the purpose of the polyfill they are testing. + // The rule treats the global `crypto` as experimental until Node 23, + // and the supported floor here is ^22.14.0. It is present and usable + // on 22, so the tests may use it. 'n/no-unsupported-features/node-builtins': 'off', }, }, diff --git a/src/hashing.test.ts b/src/hashing.test.ts index e418cd7be..1fc1f2178 100644 --- a/src/hashing.test.ts +++ b/src/hashing.test.ts @@ -1,7 +1,6 @@ import * as nobleHashes256 from '@noble/hashes/sha256'; import * as nobleHashes512 from '@noble/hashes/sha512'; import { webcrypto } from 'crypto'; -import { parse } from 'semver'; import { bytesToHex, stringToBytes } from './bytes'; import { sha256, sha512, sha384 } from './hashing'; @@ -10,18 +9,6 @@ describe('hash functions', () => { const originalSubtle = globalThis.crypto?.subtle ?? webcrypto.subtle; const originalDigest = originalSubtle?.digest?.bind(originalSubtle); - beforeEach(() => { - const isNode18 = parse(process.version)?.major === 18; - - // The global does not exist in Node 18, so we must add it. - if (isNode18) { - Object.defineProperty(globalThis, 'crypto', { - value: webcrypto, - writable: true, - }); - } - }); - afterEach(() => { Object.defineProperty(globalThis.crypto, 'subtle', { value: originalSubtle, From c7f95c924b0267e23bbc4164b9ffb72d0a43840c Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 9 Sep 2026 21:18:50 +0200 Subject: [PATCH 4/5] test: drop the webcrypto fallback, also unreachable Follow up to removing the Node 18 polyfill in the same PR. The remaining `webcrypto` usage was the other half of the same workaround: const originalSubtle = globalThis.crypto?.subtle ?? webcrypto.subtle; The fallback only mattered when globalThis.crypto could be absent, which was Node 18. The floor here is now ^22.14.0 || ^24, where the global is always present, so this reads `globalThis.crypto.subtle` and the `crypto` import goes with it. All 12 tests still pass. The node-builtins disable is still required: with it removed the rule still reports nine occurrences, because it treats the global `crypto` as experimental until Node 23. --- src/hashing.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hashing.test.ts b/src/hashing.test.ts index 1fc1f2178..473208f97 100644 --- a/src/hashing.test.ts +++ b/src/hashing.test.ts @@ -1,12 +1,11 @@ import * as nobleHashes256 from '@noble/hashes/sha256'; import * as nobleHashes512 from '@noble/hashes/sha512'; -import { webcrypto } from 'crypto'; import { bytesToHex, stringToBytes } from './bytes'; import { sha256, sha512, sha384 } from './hashing'; describe('hash functions', () => { - const originalSubtle = globalThis.crypto?.subtle ?? webcrypto.subtle; + const originalSubtle = globalThis.crypto.subtle; const originalDigest = originalSubtle?.digest?.bind(originalSubtle); afterEach(() => { From ba81eca2d4fa1d9a00faa1bd7e975d1b5d02fc8d Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 10 Sep 2026 11:28:03 +0200 Subject: [PATCH 5/5] docs: drop redundant node range sub-bullet --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f989b915..96842f57b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** Drop support for Node 18 and 20 ([#328](https://github.com/MetaMask/utils/pull/328)) - - The supported range is now `^22.14.0 || ^24`, matching core. - Bump `@ethereumjs/tx` from `^4.2.0` to `^5.4.0` ([#321](https://github.com/MetaMask/utils/pull/321)) - The deprecated `Keyring.signTransaction` now returns `LegacyTxData` rather than `TxData`. These describe the same shape: `@ethereumjs/tx@5` repurposed the name `TxData` for a map keyed by transaction type and renamed the old meaning to `LegacyTxData`. Implementations do not need changing. - Bump `@metamask/scure-bip39` from `^2.0.3` to `^2.1.1` ([#311](https://github.com/MetaMask/utils/pull/311))