feat!: release 3.0.0 — supply-chain hardening, dep cleanup, perf, new…#76
Merged
Conversation
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
rushby
force-pushed
the
release/3.0.0
branch
2 times, most recently
from
May 13, 2026 17:53
f80d650 to
0ca463f
Compare
cronicc
approved these changes
May 14, 2026
… connection options
This is a large multi-area release: CI/supply-chain hardening, removal of
dead production dependencies, performance work on the subscription and
transaction paths, broader test coverage, and new user-facing connection
options. See BREAKING CHANGE footer for the user-impacting changes.
## CI / supply-chain
- pin every third-party GitHub Action to a full commit SHA; bump
actions/checkout v3→v4.2.2 and actions/setup-node v3→v4.4.0 in the test
workflow (v3 deprecated); slack-github-action SHA-pinned at v1.25.0
- trigger ci-build-and-test on `synchronize` and `reopened` (not just
`opened`) so pushes to a PR re-run tests
- add `--ignore-scripts` to `npm ci` in both workflows; verified the dep
tree has zero transitive packages that need install scripts
- remove `cache: 'npm'` from ci-publish-package.yml (defense against
GHA cache poisoning)
- add `step-security/harden-runner` (audit mode) as the first step in
all 4 workflows
- add `.github/dependabot.yml` for weekly automated dependency PRs.
Includes `cooldown` (default 14d / 30d major / 7d minor & patch) —
matches the worm-defense intent from the original review item.
Groups @polkadot/* and @polkadot-api/* together, separates prod and
dev deps, ignores semver-major updates (must be raised manually),
and covers the github-actions ecosystem so our SHA-pinned workflow
Actions stay current. Built into GitHub — no app install needed.
- add CodeQL workflow (`javascript-typescript`, `security-extended`
query suite, push/PR/weekly cron)
- add OSV-Scanner workflow (recursive lockfile scan against the OSV
database, push/PR/weekly cron)
- generate CycloneDX SBOM at publish time via
`@cyclonedx/cyclonedx-npm --ignore-npm-errors` and attach it to the
GitHub Release as `sbom.cyclonedx.json`
- delete four stale `.d.ts` files from the src/ working tree that
shadowed the real types (not git-tracked but confused IDE LSP)
- remove the untracked `tests/common/data/ultrahonk_test/` leftover
## Dependencies
- remove unused production deps: `web3`, `snarkjs` (Library.snarkjs
refers to the local `proofTypes/groth16/formatter/snarkjs/` directory,
not the npm package; the test generator shells out to a globally
installed snarkjs CLI in CI)
- remove unused dev deps: `@types/snarkjs`, `@types/web3`, `js-sha3`,
`@types/debug`, `@typescript-eslint/eslint-plugin`,
`@typescript-eslint/parser` (the unified `typescript-eslint`
package bundles these), `eslint-config-prettier`,
`eslint-plugin-prettier` (not wired into eslint.config.mjs),
`globals`, `ts-node`, `ls-engines`, `conventional-changelog-cli`,
`@babel/cli`, `@babel/core`, `babel-plugin-add-import-extension`
- bump direct-vulnerable dev deps: axios 1.8.2 → 1.16.0,
eslint 9.25.1 → 9.39.4, fastify 4.29.1 → 5.8.5 (walletServer.js
API surface verified compatible with v5)
- bump `engines.node` `>= 18` → `>= 24` (Node 18 EOL April 2025; Node 20 EOL April 2026)
- npm audit findings dropped 44 → 9 (critical eliminated); `node_modules`
dropped from 764 to 471 packages
## Build
- eliminate the babel post-process step: add explicit `.js` extensions
to all 383 relative imports across 91 source files (file imports
→ `./foo.js`; directory imports → `./foo/index.js`). `tsc` preserves
the extensions in ESM output natively. `tsconfig.esm.json` stays on
`module: "es2020"` (avoids the dual-package hazard `nodenext`
triggers with polkadot)
- jest gains a `moduleNameMapper` regex to strip `.js` at resolution
time so tests resolve to `.ts` source
- enable `sourceMap: true` + `inlineSources: true` in tsconfig.base
so consumers reporting bugs get TS source locations in stack traces
- narrow `sideEffects` from `false` to an explicit whitelist of the
Buffer polyfill paths — preserves browser polyfill behavior while
letting bundlers tree-shake everything else
- add `eslint-plugin-security` with the recommended config; only
`detect-object-injection` is disabled (13 false positives on
typed object access patterns). `detect-possible-timing-attacks`
and `detect-non-literal-fs-filename` re-enabled — both fire on
zero current code, guarding against regression
- add `knip` 6.13.1 (`knip.json` + `npm run knip` + CI step) to catch
future dead deps/files automatically
- remove redundant `coveragePathIgnorePatterns: ['tests/']` from
jest.config.js; remove `forceExit: true` so future open-handle leaks
surface instead of being masked
- remove manual `git add` from lint-staged config (auto-staged since
v15)
## Bug fixes
- `verify()`: no longer mutates the caller's `options.domainId` from
`input.domainId`. Builds a per-call `effectiveOptions` via spread
so reusing a builder across `execute()` calls no longer leaks
state between them.
- `validateProofTypeOptions`: now a pure function; returns the
validated `ProofOptions` (possibly with runtime-version defaults
applied) instead of mutating in place. Helpers renamed to
`withDefaultedUltrahonkVersion` / `withDefaultedTeeVariant`.
- `getAggregateStatementPath` (custom RPC): switched `.toHuman()` +
`Number()` to `.toJSON()`. `.toHuman()` formats u32 with
thousand-separator commas which `Number()` parses as NaN once
`numberOfLeaves` crosses 1000. Two regression tests added (large
number preserved + `.toHuman()`-shaped response rejected).
- `formatPublicSignals`: replaced no-op validation
`pubs.some(() => false)` with a real `typeof !== 'string'` check.
Previously, a `[1, 2, 3]` (numbers, not strings) input sailed
through and crashed later inside `BigInt()` with a cryptic error.
- `handleTransaction` signAndSend callback: full body wrapped in a
protected try/catch (the early-return on Error status used to run
outside the try); `cancelTransaction(error)` itself wrapped in a
defensive try. Guarantees the async callback's returned Promise
resolves cleanly so a rejection can never escape into polkadot-api's
silent rejection swallow.
- `extractErrorMessage`: `JSON.stringify` wrapped in try/catch with
`String(err)` fallback so polkadot codecs with circular refs no
longer crash error-path code.
- `setupAccount`: pre-validates the seed phrase before
`keyring.addFromUri`. Empty/whitespace input throws with a clear
message. Substrate dev SURIs (`//Alice` etc.) are rejected on Volta
and zkVerify mainnet (allowed on `.Custom()` for local dev).
BIP39 word-count enforced (12/15/18/21/24) — 32-byte hex seeds and
mnemonic-with-derivation still work.
- `validateProofTypeOptions` plonky2 message: was "requires
'compressed' (boolean) and 'hashFunction'"; `compressed` doesn't
exist on `Plonky2Config`. Now reads "requires a 'hashFunction'
option."
- `closeSession` retry: replaced fixed 500ms × 5 sleeps with
exponential backoff (100/200/400/800ms, no sleep after final
attempt). Worst-case sleep 2500ms → 1500ms.
## Performance / refactors
- `VerificationManager`: extract the 4× duplicated
`Object.defineProperty` builder loop (`verify`, `optimisticVerify`,
`batchVerify`, `batchOptimisticVerify`) into a single
`buildProofTypeMap<T>` private helper. Adding a proof type now
requires touching only the `ProofType` enum.
- `zkVerifySession`: remove the `bindMethods` runtime introspection;
use typed arrow-property delegations
(`verify: VerificationManager['verify'] = (...args) => ...`). Drops
60–70 `.bind` calls per session construction, removes the runtime
"Method collision detected" failure mode, and uses indexed types so
signatures stay in sync with manager changes. `bindMethods` deleted
from `utils/helpers`.
- `EventManager`: hoist the `matchMap` regex/string table to a
module-level constant (`RUNTIME_EVENT_MATCHERS`); lift the per-event
matcher lookup out of the per-record inner loop.
- `EventManager._registerRuntimeEvent`: idempotent guard
(`if (this.runtimeEventHandlers.has(eventType)) return`) so the
function is safe at the function level, not just via the
`subscribedEvents` Set in `subscribe()`.
- `handleTransactionEvents`: hoist `getProofPallet(...)` lookup out
of the per-event-record `forEach`. Previously called twice per
event for Verify or VKRegistration; now resolved once before the
loop, and skipped entirely for non-proof transaction types.
- `unstringifyBigInts`: hoist regexes to module-level constants
(`DECIMAL_RE`, `HEX_RE`); cheap 2-byte prefix check routes to one
regex instead of running both; non-numeric strings return early
instead of falling through to the object branch.
- `handleTransaction`: deferred-promise pattern — `resolve`/`reject`
are captured outside the `new Promise(...)` constructor so the
constructor closure can be freed immediately. Helpers and state
live at function scope. `.then().catch()` on the signAndSend
unsubscribe handshake replaced with a single async IIFE (1 chained
Promise saved per tx).
- `waitForNodeToSync`: was an unbounded `while (isSyncing)` poll
with no timeout. Now accepts `WaitForNodeToSyncOptions { timeoutMs,
pollIntervalMs, signal }`. Defaults: 5-min total timeout, 1s poll
interval (unchanged), no signal. Throws on deadline; throws
`AbortError` (DOMException) on signal abort. New internal `sleep`
helper clears its timer on abort to avoid leaked handles.
- consolidate 6× duplicated `validateHexString` (ultrahonk, risc0,
sp1, ezkl, tee, plonky2) into a single shared helper in
`utils/helpers/index.ts`.
## New user-facing options
- `NetworkConfig.wsProvider` (new) — pass `autoConnectMs` (number |
false; default 2500) and `timeout` (number) to the WebSocket
provider. Setting `autoConnectMs: false` disables auto-reconnect
entirely; combine with `session.provider.on('disconnected', ...)`
to implement custom retry policies with a hard upper bound.
- `NetworkConfig.syncTimeoutMs` (new) — max ms to wait for the node
to finish syncing during session start. Defaults to 300_000 (5 min).
Throws if exceeded.
- `WaitForNodeToSyncOptions` exported for advanced direct use.
- README "Connection tuning" section covers both new options;
`registerDomain` JSDoc + README clarified that `queueSize: 0`
throws (the default only applies when the argument is omitted).
## Tests
- 169 tests across 24 suites at start; 181 tests across 24 suites at
end. New tests cover: rpc `.toJSON()` regression (2), formatPubs
type check (6), waitForNodeToSync timeout/abort (5), connection
config plumbing (3), handleTransactionEvents pallet-lookup hoist
+5), `_registerRuntimeEvent` idempotency (1), unstringifyBigInts
(8), handleTransaction isInvalid path (1), formatPublicSignals
utilities + unstringifyBigInts (14 in one file), seed-phrase
validation (7), extractErrorMessage (5). Three pre-existing tests
updated to match the C9 immutable-options contract.
BREAKING CHANGE: `engines.node` raised from `>= 18` to `>= 24` —
Node 18 went EOL April 2025 and Node 20 went EOL April 2026. Yarn Berry users on Node 18, 20, or 22 will get
hard install failures; npm/pnpm users get a warning unless
`engine-strict=true`.
BREAKING CHANGE: `web3`, `snarkjs`, `@types/snarkjs`, and `@types/web3`
removed from `package.json`. They were never imported from src/. Any
consumer who was relying on these as transitive deps via `zkverifyjs`
must add them explicitly.
BREAKING CHANGE: `validateProofTypeOptions` (internal but exported)
now returns `ProofOptions` instead of `void`; callers that previously
relied on in-place mutation of the input options object will need to
use the return value. The exported `bindMethods` helper has been
removed from `utils/helpers` — it was internal and not referenced by
any public consumer pattern, but it is no longer exported.
BREAKING CHANGE: `fastify` (dev dep used by the test wallet server)
bumped 4.29.1 → 5.8.5. Affects integration test setup only — no
runtime impact on the published package.
…ly-chain-audit job
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
… connection options
This is a large multi-area release: CI/supply-chain hardening, removal of
dead production dependencies, performance work on the subscription and
transaction paths, broader test coverage, and new user-facing connection
options. See BREAKING CHANGE footer for the user-impacting changes.
CI / supply-chain
synchronizeandreopened(not justopened) so pushes to a PR re-run tests--ignore-scriptstonpm ciin both workflows; verified the dep tree has zero transitive packages that need install scriptscache: 'npm'from ci-publish-package.yml (defense against GHA cache poisoning)step-security/harden-runner(audit mode) as the first step in all 4 workflows.github/dependabot.ymlfor weekly automated dependency PRs. Includescooldown(default 14d / 30d major / 7d minor & patch) — matches the worm-defense intent from the original review item. Groups @polkadot/* and @polkadot-api/* together, separates prod and dev deps, ignores semver-major updates (must be raised manually), and covers the github-actions ecosystem so our SHA-pinned workflow Actions stay current. Built into GitHub — no app install needed.javascript-typescript,security-extendedquery suite, push/PR/weekly cron)@cyclonedx/cyclonedx-npm --ignore-npm-errorsand attach it to the GitHub Release assbom.cyclonedx.json.d.tsfiles from the src/ working tree that shadowed the real types (not git-tracked but confused IDE LSP)tests/common/data/ultrahonk_test/leftoverDependencies
web3,snarkjs(Library.snarkjs refers to the localproofTypes/groth16/formatter/snarkjs/directory, not the npm package; the test generator shells out to a globally installed snarkjs CLI in CI)@types/snarkjs,@types/web3,js-sha3,@types/debug,@typescript-eslint/eslint-plugin,@typescript-eslint/parser(the unifiedtypescript-eslintpackage bundles these),eslint-config-prettier,eslint-plugin-prettier(not wired into eslint.config.mjs),globals,ts-node,ls-engines,conventional-changelog-cli,@babel/cli,@babel/core,babel-plugin-add-import-extensionengines.node>= 18→>= 20(Node 18 EOL April 2025)node_modulesdropped from 764 to 471 packagesBuild
.jsextensions to all 383 relative imports across 91 source files (file imports →./foo.js; directory imports →./foo/index.js).tscpreserves the extensions in ESM output natively.tsconfig.esm.jsonstays onmodule: "es2020"(avoids the dual-package hazardnodenexttriggers with polkadot)moduleNameMapperregex to strip.jsat resolution time so tests resolve to.tssourcesourceMap: true+inlineSources: truein tsconfig.base so consumers reporting bugs get TS source locations in stack tracessideEffectsfromfalseto an explicit whitelist of the Buffer polyfill paths — preserves browser polyfill behavior while letting bundlers tree-shake everything elseeslint-plugin-securitywith the recommended config; onlydetect-object-injectionis disabled (13 false positives on typed object access patterns).detect-possible-timing-attacksanddetect-non-literal-fs-filenamere-enabled — both fire on zero current code, guarding against regressionknip6.13.1 (knip.json+npm run knip+ CI step) to catch future dead deps/files automaticallycoveragePathIgnorePatterns: ['tests/']from jest.config.js; removeforceExit: trueso future open-handle leaks surface instead of being maskedgit addfrom lint-staged config (auto-staged since v15)Bug fixes
verify(): no longer mutates the caller'soptions.domainIdfrominput.domainId. Builds a per-calleffectiveOptionsvia spread so reusing a builder acrossexecute()calls no longer leaks state between them.validateProofTypeOptions: now a pure function; returns the validatedProofOptions(possibly with runtime-version defaults applied) instead of mutating in place. Helpers renamed towithDefaultedUltrahonkVersion/withDefaultedTeeVariant.getAggregateStatementPath(custom RPC): switched.toHuman()+Number()to.toJSON()..toHuman()formats u32 with thousand-separator commas whichNumber()parses as NaN oncenumberOfLeavescrosses 1000. Two regression tests added (large number preserved +.toHuman()-shaped response rejected).formatPublicSignals: replaced no-op validationpubs.some(() => false)with a realtypeof !== 'string'check. Previously, a[1, 2, 3](numbers, not strings) input sailed through and crashed later insideBigInt()with a cryptic error.handleTransactionsignAndSend callback: full body wrapped in a protected try/catch (the early-return on Error status used to run outside the try);cancelTransaction(error)itself wrapped in a defensive try. Guarantees the async callback's returned Promise resolves cleanly so a rejection can never escape into polkadot-api's silent rejection swallow.extractErrorMessage:JSON.stringifywrapped in try/catch withString(err)fallback so polkadot codecs with circular refs no longer crash error-path code.setupAccount: pre-validates the seed phrase beforekeyring.addFromUri. Empty/whitespace input throws with a clear message. Substrate dev SURIs (//Aliceetc.) are rejected on Volta and zkVerify mainnet (allowed on.Custom()for local dev). BIP39 word-count enforced (12/15/18/21/24) — 32-byte hex seeds and mnemonic-with-derivation still work.validateProofTypeOptionsplonky2 message: was "requires 'compressed' (boolean) and 'hashFunction'";compresseddoesn't exist onPlonky2Config. Now reads "requires a 'hashFunction' option."closeSessionretry: replaced fixed 500ms × 5 sleeps with exponential backoff (100/200/400/800ms, no sleep after final attempt). Worst-case sleep 2500ms → 1500ms.Performance / refactors
VerificationManager: extract the 4× duplicatedObject.definePropertybuilder loop (verify,optimisticVerify,batchVerify,batchOptimisticVerify) into a singlebuildProofTypeMap<T>private helper. Adding a proof type now requires touching only theProofTypeenum.zkVerifySession: remove thebindMethodsruntime introspection; use typed arrow-property delegations (verify: VerificationManager['verify'] = (...args) => ...). Drops 60–70.bindcalls per session construction, removes the runtime "Method collision detected" failure mode, and uses indexed types so signatures stay in sync with manager changes.bindMethodsdeleted fromutils/helpers.EventManager: hoist thematchMapregex/string table to a module-level constant (RUNTIME_EVENT_MATCHERS); lift the per-event matcher lookup out of the per-record inner loop.EventManager._registerRuntimeEvent: idempotent guard (if (this.runtimeEventHandlers.has(eventType)) return) so the function is safe at the function level, not just via thesubscribedEventsSet insubscribe().handleTransactionEvents: hoistgetProofPallet(...)lookup out of the per-event-recordforEach. Previously called twice per event for Verify or VKRegistration; now resolved once before the loop, and skipped entirely for non-proof transaction types.unstringifyBigInts: hoist regexes to module-level constants (DECIMAL_RE,HEX_RE); cheap 2-byte prefix check routes to one regex instead of running both; non-numeric strings return early instead of falling through to the object branch.handleTransaction: deferred-promise pattern —resolve/rejectare captured outside thenew Promise(...)constructor so the constructor closure can be freed immediately. Helpers and state live at function scope..then().catch()on the signAndSend unsubscribe handshake replaced with a single async IIFE (1 chained Promise saved per tx).waitForNodeToSync: was an unboundedwhile (isSyncing)poll with no timeout. Now acceptsWaitForNodeToSyncOptions { timeoutMs, pollIntervalMs, signal }. Defaults: 5-min total timeout, 1s poll interval (unchanged), no signal. Throws on deadline; throwsAbortError(DOMException) on signal abort. New internalsleephelper clears its timer on abort to avoid leaked handles.validateHexString(ultrahonk, risc0, sp1, ezkl, tee, plonky2) into a single shared helper inutils/helpers/index.ts.New user-facing options
NetworkConfig.wsProvider(new) — passautoConnectMs(number | false; default 2500) andtimeout(number) to the WebSocket provider. SettingautoConnectMs: falsedisables auto-reconnect entirely; combine withsession.provider.on('disconnected', ...)to implement custom retry policies with a hard upper bound.NetworkConfig.syncTimeoutMs(new) — max ms to wait for the node to finish syncing during session start. Defaults to 300_000 (5 min). Throws if exceeded.WaitForNodeToSyncOptionsexported for advanced direct use.registerDomainJSDoc + README clarified thatqueueSize: 0throws (the default only applies when the argument is omitted).Tests
.toJSON()regression (2), formatPubs type check (6), waitForNodeToSync timeout/abort (5), connection config plumbing (3), handleTransactionEvents pallet-lookup hoist +5),_registerRuntimeEventidempotency (1), unstringifyBigInts (8), handleTransaction isInvalid path (1), formatPublicSignals utilities + unstringifyBigInts (14 in one file), seed-phrase validation (7), extractErrorMessage (5). Three pre-existing tests updated to match the C9 immutable-options contract.BREAKING CHANGE:
engines.noderaised from>= 18to>= 24—Node 18 went EOL April 2025. Yarn Berry users on Node 18 will get
hard install failures; npm/pnpm users get a warning unless
engine-strict=true.BREAKING CHANGE:
web3,snarkjs,@types/snarkjs, and@types/web3removed from
package.json. They were never imported from src/. Anyconsumer who was relying on these as transitive deps via
zkverifyjsmust add them explicitly.
BREAKING CHANGE:
validateProofTypeOptions(internal but exported)now returns
ProofOptionsinstead ofvoid; callers that previouslyrelied on in-place mutation of the input options object will need to
use the return value. The exported
bindMethodshelper has beenremoved from
utils/helpers— it was internal and not referenced byany public consumer pattern, but it is no longer exported.
BREAKING CHANGE:
fastify(dev dep used by the test wallet server)bumped 4.29.1 → 5.8.5. Affects integration test setup only — no
runtime impact on the published package.