From ffafeb0236343cc68afea8e507516a2fc74222fa Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 09:13:32 -0600 Subject: [PATCH 1/8] node 24 --- .changeset/node-24-cli.md | 5 +++++ .changeset/node-24-dev-utils.md | 5 +++++ .changeset/node-24-viem-account-ledger.md | 5 +++++ .github/actions/sync-workspace/action.yml | 4 ++-- .github/workflows/changeset-for-renovate.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yaml | 4 ++-- .github/workflows/upload-celocli-executables.yml | 16 ++++++++-------- .nvmrc | 2 +- package.json | 2 +- packages/cli/README.md | 2 +- packages/cli/package.json | 2 +- packages/cli/standalone.Dockerfile | 2 +- packages/viem-account-ledger/package.json | 2 +- 14 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 .changeset/node-24-cli.md create mode 100644 .changeset/node-24-dev-utils.md create mode 100644 .changeset/node-24-viem-account-ledger.md diff --git a/.changeset/node-24-cli.md b/.changeset/node-24-cli.md new file mode 100644 index 0000000000..1e1d334f61 --- /dev/null +++ b/.changeset/node-24-cli.md @@ -0,0 +1,5 @@ +--- +"@celo/celocli": patch +--- + +Update minimum Node.js version requirement to >=20 and test on Node 24 diff --git a/.changeset/node-24-dev-utils.md b/.changeset/node-24-dev-utils.md new file mode 100644 index 0000000000..92ac10b24f --- /dev/null +++ b/.changeset/node-24-dev-utils.md @@ -0,0 +1,5 @@ +--- +"@celo/dev-utils": patch +--- + +Update minimum Node.js version requirement to >=20 diff --git a/.changeset/node-24-viem-account-ledger.md b/.changeset/node-24-viem-account-ledger.md new file mode 100644 index 0000000000..23ec1df87b --- /dev/null +++ b/.changeset/node-24-viem-account-ledger.md @@ -0,0 +1,5 @@ +--- +"@celo/viem-account-ledger": patch +--- + +Update minimum Node.js version requirement from >=18 to >=20 diff --git a/.github/actions/sync-workspace/action.yml b/.github/actions/sync-workspace/action.yml index 2322b20ea9..86b9ee0cbc 100644 --- a/.github/actions/sync-workspace/action.yml +++ b/.github/actions/sync-workspace/action.yml @@ -13,14 +13,14 @@ runs: steps: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' - name: "enable corepack for yarn" run : sudo corepack enable yarn shell: bash # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'yarn' - uses: actions/cache/restore@v3 id: cache_node diff --git a/.github/workflows/changeset-for-renovate.yml b/.github/workflows/changeset-for-renovate.yml index a29ca0778c..de09b86865 100644 --- a/.github/workflows/changeset-for-renovate.yml +++ b/.github/workflows/changeset-for-renovate.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' - uses: actions/checkout@v4 - name: "Run changesets-renovate" # when on a renovate branch which modified the package.json for a workspace this will create a changeset for those changes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddefd00f18..c20351f961 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,14 +52,14 @@ jobs: steps: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' - name: 'enable corepack for yarn' run: sudo corepack enable yarn - uses: actions/checkout@v4 # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'yarn' - name: Restore node cache uses: actions/cache@v4 @@ -452,7 +452,7 @@ jobs: - name: "Setup Node" uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Validate Team Config uses: suzuki-shunsuke/github-action-renovate-config-validator@v1.0.1 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index da419af876..90c62476ad 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -138,7 +138,7 @@ jobs: needs: release runs-on: ['self-hosted', 'org', 'ubuntu-22-04'] container: - image: node:20-bullseye + image: node:24-bookworm outputs: result: '${{ steps.map.outputs.output }}' steps: @@ -163,7 +163,7 @@ jobs: if: needs.release.outputs.published == 'true' && needs.prepare.outputs.result runs-on: ['self-hosted', 'org', 'ubuntu-22-04'] container: - image: node:20-bullseye + image: node:24-bookworm strategy: fail-fast: false max-parallel: 12 diff --git a/.github/workflows/upload-celocli-executables.yml b/.github/workflows/upload-celocli-executables.yml index f2b0f7270b..8da09fcb20 100644 --- a/.github/workflows/upload-celocli-executables.yml +++ b/.github/workflows/upload-celocli-executables.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: 'enable corepack for yarn' run: sudo corepack enable yarn @@ -41,7 +41,7 @@ jobs: # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: 'yarn' - name: Install Dependencies @@ -111,17 +111,17 @@ jobs: ref: ${{ inputs.ref }} fetch-depth: 0 fetch-tags: true - - name: Setup Node.js 20.x + - name: Setup Node.js 24.x uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: 'enable corepack for yarn' run: corepack enable yarn shell: bash # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'yarn' - name: Install Dependencies shell: bash @@ -184,17 +184,17 @@ jobs: ref: main token: ${{ env.HOMEBREW_BREW_TOKEN }} - - name: Setup Node.js 20.x + - name: Setup Node.js 24.x uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: 'enable corepack for yarn' run: sudo corepack enable yarn shell: bash # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'yarn' - name: Install Dependencies shell: bash diff --git a/.nvmrc b/.nvmrc index 2bd5a0a98a..a45fd52cc5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +24 diff --git a/package.json b/package.json index 172d038f20..4981290282 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@biomejs/biome": "2.0.5", "@celo/typescript": "workspace:^", "@changesets/changelog-github": "^0.5.1", - "@types/node": "18.7.16", + "@types/node": "24.0.0", "colors": "1.4.0", "husky": "^8.0.0", "jest": "^29.7.0", diff --git a/packages/cli/README.md b/packages/cli/README.md index 974a6aecf0..a830f32506 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -4,7 +4,7 @@ Tool for interacting with the Celo Protocol. ## Installation -celocli is tested on node 20, although we find it works with warnings on 18 and 22 as well. +celocli is tested on node 24. To install globally, run: diff --git a/packages/cli/package.json b/packages/cli/package.json index 2848e6a7fa..f014eb4678 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -23,7 +23,7 @@ "celo-cli" ], "engines": { - "node": ">=18" + "node": ">=20" }, "scripts": { "clean": "rm -f tsconfig.tsbuildinfo && rm -rf lib/ && yarn run --top-level tsc -b . --clean", diff --git a/packages/cli/standalone.Dockerfile b/packages/cli/standalone.Dockerfile index ab805a5bdb..e06ad2c5d7 100644 --- a/packages/cli/standalone.Dockerfile +++ b/packages/cli/standalone.Dockerfile @@ -3,7 +3,7 @@ # Example build command (manual): # # VERSION=x.y.z; docker build . --build-arg VERSION=$VERSION -t celocli-standalone:$VERSION -FROM node:20-alpine +FROM node:24-alpine LABEL org.opencontainers.image.authors="devops@clabs.co" # Install cli install dependencies. diff --git a/packages/viem-account-ledger/package.json b/packages/viem-account-ledger/package.json index 93da4a4358..34c0fc5919 100644 --- a/packages/viem-account-ledger/package.json +++ b/packages/viem-account-ledger/package.json @@ -62,6 +62,6 @@ "vitest": "^3.1.3" }, "engines": { - "node": ">=18" + "node": ">=20" } } From cbd3dc7a0dce6b6a1cb2f73c9c5fc913b5c66c40 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 10:34:27 -0600 Subject: [PATCH 2/8] temp log --- packages/cli/src/utils/checks.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/utils/checks.ts b/packages/cli/src/utils/checks.ts index ca116f0c28..d0526c6257 100644 --- a/packages/cli/src/utils/checks.ts +++ b/packages/cli/src/utils/checks.ts @@ -610,11 +610,12 @@ class CheckBuilder { this.withValidators(async (validators, _signer, account) => { const group = await getValidatorGroup(await this.getClient(), account) const [_, duration] = await validators.read.getGroupLockedGoldRequirements() - const waitPeriodEnd = group.membersUpdated.plus(bigintToBigNumber(duration)).toNumber() - const isDeregisterable = waitPeriodEnd < Date.now() / 1000 + const waitPeriodEnd = group.membersUpdated.plus(bigintToBigNumber(duration)) + const isDeregisterable = waitPeriodEnd.isLessThan(Date.now() / 1000) + console.log({waitPeriodEnd,membersUpdated: group.membersUpdated.toNumber(), duration, now: Date.now() / 1000, isDeregisterable}) if (!isDeregisterable) { console.warn( - `Group will be able to be deregistered: ${new Date(waitPeriodEnd * 1000).toUTCString()}` + `Group will be able to be deregistered: ${new Date(waitPeriodEnd.multipliedBy(1000).toNumber()).toUTCString()}` ) } return isDeregisterable From 16df75b151cb50383d5c2328fdf24283b979953d Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 10:35:05 -0600 Subject: [PATCH 3/8] amchai --- packages/cli/src/utils/checks.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/utils/checks.ts b/packages/cli/src/utils/checks.ts index d0526c6257..59b59b122d 100644 --- a/packages/cli/src/utils/checks.ts +++ b/packages/cli/src/utils/checks.ts @@ -612,7 +612,13 @@ class CheckBuilder { const [_, duration] = await validators.read.getGroupLockedGoldRequirements() const waitPeriodEnd = group.membersUpdated.plus(bigintToBigNumber(duration)) const isDeregisterable = waitPeriodEnd.isLessThan(Date.now() / 1000) - console.log({waitPeriodEnd,membersUpdated: group.membersUpdated.toNumber(), duration, now: Date.now() / 1000, isDeregisterable}) + console.log({ + waitPeriodEnd, + membersUpdated: group.membersUpdated.toNumber(), + duration, + now: Date.now() / 1000, + isDeregisterable, + }) if (!isDeregisterable) { console.warn( `Group will be able to be deregistered: ${new Date(waitPeriodEnd.multipliedBy(1000).toNumber()).toUTCString()}` From a42d9c88155858b9c26f8ca13d3320fbf5b12d8e Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 10:37:44 -0600 Subject: [PATCH 4/8] snapshot --- yarn.lock | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 4f3ff808d1..78d6a0b8c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6634,6 +6634,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:24.0.0": + version: 24.0.0 + resolution: "@types/node@npm:24.0.0" + dependencies: + undici-types: "npm:~7.8.0" + checksum: 5e7b5184c67964cb4fa74f9fcc1f936e101d9858c9559cea75ee50558ae100f37bd4eef807d2d7fbdc413f191f74a6066f57a4d2acbd4df64f4a76b933302b52 + languageName: node + linkType: hard + "@types/node@npm:^12.12.6, @types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" @@ -8181,7 +8190,7 @@ __metadata: "@celo/typescript": "workspace:^" "@changesets/changelog-github": "npm:^0.5.1" "@changesets/cli": "npm:^2.29.5" - "@types/node": "npm:18.7.16" + "@types/node": "npm:24.0.0" colors: "npm:1.4.0" husky: "npm:^8.0.0" jest: "npm:^29.7.0" @@ -17338,6 +17347,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.8.0": + version: 7.8.0 + resolution: "undici-types@npm:7.8.0" + checksum: fcff3fbab234f067fbd69e374ee2c198ba74c364ceaf6d93db7ca267e784457b5518cd01d0d2329b075f412574205ea3172a9a675facb49b4c9efb7141cd80b7 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" From 383fe66669a72a257e68caf2b18a87be6a1fb6b8 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 10:49:16 -0600 Subject: [PATCH 5/8] Install native build dependencies for node-hid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix yarn install failure on CI by installing required build tools (make, gcc, pkg-config) and USB/udev libraries needed to compile the node-hid native module used by @ledgerhq/hw-transport-node-hid. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c20351f961..f98c614dae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,8 @@ jobs: node-version: '24' - name: 'enable corepack for yarn' run: sudo corepack enable yarn + - name: Install native build dependencies + run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config libusb-1.0-0-dev libudev-dev - uses: actions/checkout@v4 # must call twice because of chicken and egg problem with yarn and node - uses: actions/setup-node@v4 From bd6015b2081d4ec7e2419e8211f5aa7035035015 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 11:39:16 -0600 Subject: [PATCH 6/8] docs(changeset): fix access of rawvalues --- .changeset/sad-aliens-tap.md | 5 +++++ packages/sdk/wallets/wallet-base/src/signing-utils.ts | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/sad-aliens-tap.md diff --git a/.changeset/sad-aliens-tap.md b/.changeset/sad-aliens-tap.md new file mode 100644 index 0000000000..5955c21bf8 --- /dev/null +++ b/.changeset/sad-aliens-tap.md @@ -0,0 +1,5 @@ +--- +'@celo/wallet-base': patch +--- + +fix access of rawvalues diff --git a/packages/sdk/wallets/wallet-base/src/signing-utils.ts b/packages/sdk/wallets/wallet-base/src/signing-utils.ts index 9812f3de00..54da4fb5ce 100644 --- a/packages/sdk/wallets/wallet-base/src/signing-utils.ts +++ b/packages/sdk/wallets/wallet-base/src/signing-utils.ts @@ -29,7 +29,7 @@ import { secp256k1 } from '@noble/curves/secp256k1' import { keccak_256 } from '@noble/hashes/sha3' import { bytesToHex, hexToBytes } from '@noble/hashes/utils' import debugFactory from 'debug' -import Web3 from 'web3' // TODO try to do this without web3 direct +import Web3 from 'web3'; // TODO try to do this without web3 direct type OldTransactionTypes = 'celo-legacy' | 'cip42' | TransactionTypes type LegacyCeloTx = Omit & { @@ -442,9 +442,9 @@ export function extractSignature(rawTx: string) { function extractSignatureFromDecoded(rawValues: Uint8Array[]) { // signature is always (for the tx we support so far) the last three elements of the array in order v, r, s, - const v = rawValues.at(-3) - const r = rawValues.at(-2) - const s = rawValues.at(-1) + const v = rawValues[rawValues.length - 3] + const r = rawValues[rawValues.length - 2] + const s = rawValues[rawValues.length - 1] // https://github.com/wagmi-dev/viem/blob/993321689b3e2220976504e7e170fe47731297ce/src/utils/transaction/parseTransaction.ts#L281 // Account.recover cannot handle canonicalized signatures // A canonicalized signature may have the first byte removed if its value is 0 From 87799496d6d0fe8b1e460872f295b0bb2784d561 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 12:15:32 -0600 Subject: [PATCH 7/8] fix timetest --- .../src/commands/validatorgroup/deregister.test.ts | 11 ++++++++++- packages/cli/src/utils/checks.ts | 7 ------- packages/sdk/wallets/wallet-base/src/signing-utils.ts | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/commands/validatorgroup/deregister.test.ts b/packages/cli/src/commands/validatorgroup/deregister.test.ts index 286ccd236e..fc215df7e1 100644 --- a/packages/cli/src/commands/validatorgroup/deregister.test.ts +++ b/packages/cli/src/commands/validatorgroup/deregister.test.ts @@ -81,6 +81,15 @@ testWithAnvilL2('validatorgroup:deregister cmd', (web3: Web3) => { }) describe('when not enough time has passed', () => { it('shows error that wait period is not over', async () => { + // Mock Date.now() to ensure we're before the wait period ends + // This prevents flakiness on CI where real time may have passed + const validators = await kit.contracts.getValidators() + const group = await validators.getValidatorGroup(groupAddress) + const groupRequirements = await validators.getGroupLockedGoldRequirements() + const waitPeriodEnd = group.membersUpdated + (groupRequirements.duration.toNumber()) + const mockNow = (waitPeriodEnd - 1000) * 1000 + const timeSpy = jest.spyOn(global.Date, 'now').mockImplementation(() => mockNow) + const logMock = jest.spyOn(console, 'log').mockImplementation() logMock.mockClear() await expect( @@ -105,8 +114,8 @@ testWithAnvilL2('validatorgroup:deregister cmd', (web3: Web3) => { ], ] `) - const validators = await kit.contracts.getValidators() await expect(validators.isValidatorGroup(groupAddress)).resolves.toBe(true) + timeSpy.mockRestore() }) }) describe('when wait duration for unlocking is over', () => { diff --git a/packages/cli/src/utils/checks.ts b/packages/cli/src/utils/checks.ts index 59b59b122d..30ae833098 100644 --- a/packages/cli/src/utils/checks.ts +++ b/packages/cli/src/utils/checks.ts @@ -612,13 +612,6 @@ class CheckBuilder { const [_, duration] = await validators.read.getGroupLockedGoldRequirements() const waitPeriodEnd = group.membersUpdated.plus(bigintToBigNumber(duration)) const isDeregisterable = waitPeriodEnd.isLessThan(Date.now() / 1000) - console.log({ - waitPeriodEnd, - membersUpdated: group.membersUpdated.toNumber(), - duration, - now: Date.now() / 1000, - isDeregisterable, - }) if (!isDeregisterable) { console.warn( `Group will be able to be deregistered: ${new Date(waitPeriodEnd.multipliedBy(1000).toNumber()).toUTCString()}` diff --git a/packages/sdk/wallets/wallet-base/src/signing-utils.ts b/packages/sdk/wallets/wallet-base/src/signing-utils.ts index 54da4fb5ce..5f65ebc178 100644 --- a/packages/sdk/wallets/wallet-base/src/signing-utils.ts +++ b/packages/sdk/wallets/wallet-base/src/signing-utils.ts @@ -29,7 +29,7 @@ import { secp256k1 } from '@noble/curves/secp256k1' import { keccak_256 } from '@noble/hashes/sha3' import { bytesToHex, hexToBytes } from '@noble/hashes/utils' import debugFactory from 'debug' -import Web3 from 'web3'; // TODO try to do this without web3 direct +import Web3 from 'web3' // TODO try to do this without web3 direct type OldTransactionTypes = 'celo-legacy' | 'cip42' | TransactionTypes type LegacyCeloTx = Omit & { From eeed388e43e658595d30f980e3afe942443e5c67 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 6 Jan 2026 12:16:06 -0600 Subject: [PATCH 8/8] fmt --- packages/cli/src/commands/validatorgroup/deregister.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/validatorgroup/deregister.test.ts b/packages/cli/src/commands/validatorgroup/deregister.test.ts index fc215df7e1..44b541ad95 100644 --- a/packages/cli/src/commands/validatorgroup/deregister.test.ts +++ b/packages/cli/src/commands/validatorgroup/deregister.test.ts @@ -86,7 +86,7 @@ testWithAnvilL2('validatorgroup:deregister cmd', (web3: Web3) => { const validators = await kit.contracts.getValidators() const group = await validators.getValidatorGroup(groupAddress) const groupRequirements = await validators.getGroupLockedGoldRequirements() - const waitPeriodEnd = group.membersUpdated + (groupRequirements.duration.toNumber()) + const waitPeriodEnd = group.membersUpdated + groupRequirements.duration.toNumber() const mockNow = (waitPeriodEnd - 1000) * 1000 const timeSpy = jest.spyOn(global.Date, 'now').mockImplementation(() => mockNow)