feat: compact-deployer tool#86
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
⚠️ 9 New Security Findings
The latest commit contains 9 new security findings.
| Findings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Dependency: yarn / brace-expansion@ 2.0.2 SUMMARY Direct Dependency: brace-expansion Location : yarn.lock OCCURRENCE
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / ip-address@ 10.0.1 SUMMARY Direct Dependency: ip-address Location : yarn.lock OCCURRENCE
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / minimatch@ 9.0.5 SUMMARY Direct Dependency: minimatch Location : yarn.lock OCCURRENCES
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / picomatch@ 4.0.3 SUMMARY Direct Dependency: picomatch Location : yarn.lock OCCURRENCES
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / postcss@ 8.5.6 SUMMARY Direct Dependency: postcss Location : yarn.lock OCCURRENCE
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / rollup@ 4.52.5 SUMMARY Direct Dependency: rollup Location : yarn.lock OCCURRENCE
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / tar@ 7.5.3 SUMMARY Direct Dependency: tar Location : yarn.lock OCCURRENCES
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / undici@ 5.29.0 SUMMARY Direct Dependency: undici Location : yarn.lock OCCURRENCES
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: yarn / vite@ 7.1.12 SUMMARY Direct Dependency: vite Location : yarn.lock OCCURRENCES
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
Not a finding? Ignore it by adding a comment on the line with just the word noboost.
Scanner: boostsecurity - Trivy (Filesystem scanning)
There was a problem hiding this comment.
⚠️ 10 New Security Findings
The latest commit contains 10 new security findings.
| Findings | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Dependency: npm / @substrate/connect@ 0.8.11 SUMMARY Dependency: @substrate/connect Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / glob@ 10.5.0 SUMMARY Dependency: glob Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / minimatch@ 9.0.5 SUMMARY Dependency: minimatch Transitive through:
Location : yarn.lock OCCURRENCES
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / node-domexception@ 1.0.0 SUMMARY Dependency: node-domexception Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / picomatch@ 4.0.3 SUMMARY Dependency: picomatch Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / rollup@ 4.52.5 SUMMARY Dependency: rollup Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / tar@ 7.5.3 SUMMARY Dependency: tar Transitive through:
Location : yarn.lock OCCURRENCES
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / undici@ 5.29.0 SUMMARY Dependency: undici Transitive through:
Location : yarn.lock OCCURRENCES
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / uuid@ 10.0.0 SUMMARY Dependency: uuid Transitive through:
Location : yarn.lock OCCURRENCE
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dependency: npm / vite@ 7.1.12 SUMMARY Dependency: vite Transitive through:
Location : yarn.lock OCCURRENCES
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Remediation :
|
Not a finding? Ignore it by adding a comment on the line with just the word noboost.
Scanner: boostsecurity - BoostSecurity SCA
Adds @openzeppelin/compact-deploy — a Forge-style deployer library for Midnight Compact contracts — and exposes its CLI through the existing @openzeppelin/compact-cli package. Library (packages/deploy/): - runPipeline orchestrator (config → wallet → faucet → providers → submit → persist), decomposed into per-step helpers - Deployments class — head + history JSON ledger with read methods (getHead, getHistory, listContracts) and atomic rotation on write - Keystore class — Web3 Secret Storage v3-compatible with a "midnight-1" version marker; scrypt + AES-128-CTR + SHA-256 MAC - ProofServer class — lifecycle wrapper over the five-step precedence chain (CLI > TOML URL > "auto" container > PROOF_SERVER_PORT > default) - Typed error hierarchy with stable exit codes (DeployError + subclasses) - src/ layout: loaders/, config/, wallet/, providers/ + top-level errors.ts / pipeline.ts / deployments.ts / index.ts CLI (packages/cli/): - runDeploy.ts: thin shell over the deploy library (chalk + ora + pino UX, --json mode, exit-code-mapped errors); ~250 LOC, zero business logic - compact-deploy bin entry added alongside compact-builder + compact-compiler - engines.node bumped 20 → 22 to match the deploy library Integration tests (tests/integrations/): - vitest config + wallet pool harness + Counter fixture - specs cover deploy, dry-run, history rotation, errors, wallet pool Root: - @openzeppelin/compact-deploy workspace devDep - test:integration / env:up / env:down scripts - resolutions pin for @midnight-ntwrk/ledger-v8 8.0.3
- Replace the four loader functions with classes that own their loaded
values: SigningKey, ConstructorArgs, InitialPrivateState, Artifact.
Each exposes `static async load()` + readonly fields; pipeline reads
`.hex` / `.values` / `?.value` at the call site.
- Add shared LoaderContext + RefResolver helpers under loaders/ to absorb
path resolution, readFile, dynamic-import error wrapping, and the
{ file } | { module, export } dispatch.
- Promote loadConfig + LoadedConfig to a CompactConfig class with
`network(name)` / `contract(name)` lookups that throw with the
available set on miss. resolveTargets collapses; wallet/resolve.ts
drops its redundant rootDir parameter (derived from config.rootDir).
- Rename Zod-inferred CompactConfig -> CompactConfigData (internal);
the public name is now the class.
Lets callers manage the proof-server container with `await using` and AsyncDisposableStack instead of explicit try/finally for teardown. Dispose errors are warn-logged rather than thrown so a failed teardown doesn't mask the deploy's primary failure.
AsyncDisposableStack landed in Node 24.0; Node 22 only ships Symbol.asyncDispose and `await using`. Realigns engines with the @tsconfig/node24 lib defs already in use across these packages.
…lasses; consolidate seed helpers
Reshapes the deploy entry-point around resource-managed classes:
- `Deployer` (deployer.ts) replaces the procedural `runPipeline`
function. `Deployer.prepare(opts)` loads config + signing key, starts
the proof server, builds or adopts a wallet, and loads constructor
args; the returned instance exposes `.deploy()` and `.dryRun()`. CLI
flow becomes:
await using deployer = await Deployer.prepare(opts);
return dryRun ? await deployer.dryRun() : await deployer.deploy();
Owned resources are accumulated in an `AsyncDisposableStack` inside
`prepare`, moved to the instance on success, and disposed in reverse
order from `[Symbol.asyncDispose]`. Mid-prepare failures unwind
cleanly via the local `await using` on the stack.
- `WalletHandler` (wallet/handler.ts) replaces the
`buildDeployerWallet` free function. It wraps a built
`MidnightWalletProvider` and implements `[Symbol.asyncDispose]`, so
the Deployer adds it to its stack with a single `stack.use(owned)`
instead of `stack.defer(...)` with custom try/catch. Mirrors the
`ProofServer` pattern.
- `wallet/seeds.ts` merges the previous `local-seeds.ts`,
`normalize.ts`, and `resolve.ts` into one module. None of those owned
state or a lifecycle, so a class wrapper would be ceremony; a single
file with three exports is the right unit. Test renamed
`normalize.test.ts` -> `seeds.test.ts`.
Public API: `runPipeline as deploy` and `PipelineOptions/PipelineResult`
are gone; consumers now use `Deployer`, `DeployerOptions`,
`DeployResult`. `buildDeployerWallet` replaced by `WalletHandler`.
Integration harness and CLI updated.
Mock-based unit tests covering orchestration semantics that integration tests can't isolate cheaply: - Deployer (6 tests): dryRun returns dryRun:true and never calls deployContract; deploy submits the tx and returns the populated success result; injected walletProvider is adopted without calling WalletHandler.build; missing walletProvider builds and starts a wallet; Symbol.asyncDispose stops owned wallets but leaves injected ones alone; deployContract failures are wrapped in DeployTxFailedError. - WalletHandler (7 tests): mnemonic seed routes through .withMnemonic; hex seed routes through .withSeed; additionalFeeOverhead bumps to 5e17 for the undeployed network and keeps the testkit default otherwise; .provider returns the wallet built by MidnightWalletProvider.withWallet; Symbol.asyncDispose stops the underlying wallet; dispose swallows stop() failures with a warn log. testkit-js + ledger-v8 + midnight-js are vi.mock'd; CompactConfig, SigningKey, Deployments run against real tmpdir fixtures. End-to-end network flow remains covered by tests/integrations/.
Renames the npm package from `@openzeppelin/compact-deploy` to `@openzeppelin/compact-deployer`. Updates workspace deps in root + compact-cli, all `from '@openzeppelin/compact-deploy'` imports across the CLI, integration harness and specs, the JSDoc/README references, and the regenerated yarn.lock. The `compact-deploy` binary name and the `packages/deploy/` directory layout are intentionally left unchanged.
…oyer Aligns the workspace folder layout with the @openzeppelin/compact-deployer package name. Git tracks every file as a rename so blame is preserved. No path references needed updating — the yarn workspaces glob is `packages/*`, and no script or tsconfig hardcoded `packages/deploy`.
…... style Convention sweep across all unit + integration test files. No test behaviour changes — only the strings passed to `it(...)` (and the `it.each(...)` template in walletPool.spec.ts). All 50 unit tests still pass.
Five specs are reorganised by feature theme to make the suite scale:
specs/
deploy/
deploy.spec.ts
dryRun.spec.ts
historyRotation.spec.ts
errors/
errors.spec.ts
wallet/
walletPool.spec.ts
No behaviour change — only relative-import depth bumps from `../_harness`
to `../../_harness`. The existing `specs/**/*.spec.ts` glob in vitest.config
already picks up nested directories.
958d227 to
26aa42b
Compare
…ignore-all Biome ci was failing with 23 errors + 1 warning across the deployer package and integration harness — mostly `useImportType` and `organizeImports` autofixes. `yarn lint:fix` resolved 19 of them; the remaining 4 were: - `packages/cli/src/runDeploy.ts` × 3 — `lint/suspicious/noConsole`. organizeImports had moved the `@openzeppelin/compact-deployer` import ahead of the `biome-ignore-all noConsole` directive, so the directive no longer applied to the file. Moved the directive to line 2 (right after the shebang, before all imports). - `packages/deployer/src/deployer.ts` — dead `SeedResolution` type import after the seeds-module merge consolidated the type alias. Lint:ci now clean; 50 unit tests still pass.
…ions Addresses the BoostSecurity findings on PR #86. Eight of the originally flagged packages auto-upgraded when yarn.lock regenerated post-rebase (brace-expansion, ip-address, minimatch, picomatch, postcss, rollup, tar, vite). The remaining three are forced via root resolutions: - undici ^6.24.0 (was 5.29.0 via testcontainers) Fixes CVE-2026-1525/1526/1527/22036/2229 — HTTP smuggling, WebSocket memory exhaustion, CRLF injection, decompression DoS. Major bump but testcontainers' usage stays within the v6 API. - glob ^11.0.0 (was 10.5.0 via archiver-utils, cacache) Replaces the EOL v10 line. - uuid ^13.0.0 (was 10.0.0 via dockerode) Replaces the EOL v10 line. Two findings left unaddressed: - @substrate/connect@0.8.11 (EOL warning). Pinned exactly by `@polkadot/rpc-provider@16.5.6` (constraint is `0.8.11`, not a range), so resolutions can't override it without breaking that transitive. Would need to wait for @PolkaDot to publish a release that drops the EOL dep. - node-domexception@1.0.0 (EOL warning). Pulled by `fetch-blob@3.2.0`. Modern Node has a native DOMException; the package only matters until fetch-blob/undici upstream drops the polyfill import. No safe override exists. Both are warning-level (EOL classification, no active CVE). Build + 50 unit tests + CLI typecheck still pass after resolutions.
When running multiple wallets + the deployer in one process, the default levelPrivateStateProvider hits fcntl LOCK contention on midnight-level-db/. Expose privateStateProvider as a DeployerOptions field so tests can inject inMemoryPrivateStateProvider() from testkit-js and avoid the lock entirely; buildProviders falls back to constructing the LevelDB-backed provider when no override is given. Also inline the dryRunResult/successResult/logDryRun helpers into the action methods so deploy.ts reads top-to-bottom without three near-empty helper functions.
…oot Makefile
Adds:
- PrivateCounter fixture exercising init_private_state + witnesses =
{ module, export } paths end-to-end
- asyncDisposeCleanup spec: failure mid-prepare unwinds via
AsyncDisposableStack
- historyIsolation spec: same artifact under two contract names keeps
independent head/history slots
- proofServerAuto spec: DynamicProofServerContainer lifecycle
- keystorePassphrase + walletLifecycle wallet specs
- SecondaryCounter contract entry sharing the Counter artifact
Harness updates: shared inMemoryPrivateStateProvider per deploy, syncWallet
between back-to-back deploys to avoid stale UTXO views.
Consolidates orchestration into a single root Makefile (env-up, env-down,
compile, test-integration). The integration Makefile is removed; yarn
scripts now delegate to make so CI and local flows go through the same
trap-protected recipe.
…loy/ secrets Adds a top-level compact.toml with a [networks.preprod] block populated from testkit-js's PreprodTestEnvironment (URLs taken verbatim from the installed @midnight-ntwrk/testkit-js bundle, including the /api/v4 graphql path and the full /api/request-tokens faucet URL — the README example showed v3 and a bare host, both stale). Ignores deploy/*.seed, *.signingkey, and *.keystore.json so wallet material doesn't accidentally land in commits. The local integration tests still use tests/integrations/compact.toml; this root file is for real-network deploys driven from the repo root.
Two intertwined changes that together make 'deploy a Compact contract to
preprod with a funded seed' actually work from compact-deployer.
Wallet-state cache (the structural fix):
- WalletHandler.build now bypasses FluentWalletBuilder so it can branch
the shielded sub-wallet between WalletFactory.createShieldedWallet
(fresh) and WalletFactory.restoreShieldedWallet (resumed from
'./.states/<network>-<seed-hash>.gz').
- WalletHandler.saveCache() snapshots the shielded sub-wallet via
WalletSaveStateProvider after every successful sync; deployer.ts
calls it best-effort, never blocks the deploy on a cache write.
- Cache filename derives from network id + SHA-256 of the seed bytes
(testkit-js's own getWalletStateFilename embeds the seed verbatim;
we don't).
- On any restore failure (file missing, corrupt, SDK version drift)
the handler warns and falls through to a fresh build.
- --no-cache CLI flag (DeployerOptions.skipWalletCache) forces a
fresh sync.
- .gitignore covers .states/ and **/.states/.
Observable sync (the UX fix):
- syncAndVerifyFunds replaces testkit-js's syncWallet with an in-house
Rx pipeline so we can (a) lift the hardcoded 90s timeout to a
configurable --sync-timeout (default 10m, exposed as
DeployerOptions.syncTimeoutMs), (b) gate on state.isSynced
(same condition, cleaner), and (c) emit progress + balance lines
instead of testkit-js's per-emission flag spam.
- Throttled (30s) 'Still syncing — Nm Xs elapsed' line shows
applied/highest event counts + percentage + balance per sub-wallet
so a multi-minute preprod sync looks alive rather than hung.
- Edge-triggered 'X sync complete — balance' lines fire the moment
each sub-wallet reaches strict-complete, so users with NIGHT see
their balance the instant unshielded finishes (~30s) instead of
waiting for the full sync (~hours).
- logWalletAddresses prints all three bech32m addresses (shielded /
unshielded / dust) right after wallet.start(false), so the user can
sanity-check the deployer derived the seed they intended *before*
committing to a long sync.
- UnfundedWalletError gate now accepts shielded > 0 OR unshielded > 0
(was shielded-only); matches midnight-apps's CLI pattern and
preprod-faucet-funded wallets carry NIGHT not shielded.
README + tests updated to match. Handler tests rewritten end-to-end
since WalletHandler no longer wraps FluentWalletBuilder; deployer
tests' fake provider/handler grew wallet.state() observable + saveCache
spy + unshielded balances.
Mirror the shielded-state cache for the dust sub-wallet so subsequent runs against a real network resume from the previous run's stopping point instead of re-walking the unfiltered dustLedgerEvents stream from id=0. First preprod run still pays the full ~1h+ dust sync; every later run boots in seconds. Also add a periodic checkpoint inside syncAndVerifyFunds that snapshots both sub-wallets every 5 minutes during sync (separate from the final post-sync save). A Ctrl+C in the middle of a long first-run loses at most one checkpoint interval, not the whole run. Cache filename layout switched from `<network>-<seed-hash>.gz` to `<network>-<seed-hash>-<kind>.gz` so shielded and dust snapshots don't collide. testkit-js exposes restoreShieldedWallet but no equivalent restoreDustWallet, so dust restore routes through `DustWallet(config).restore(serializedState)` directly. WalletHandler also now exposes the unshielded keystore so downstream helpers (e.g. upcoming DustBootstrap) can sign NIGHT payloads without re-deriving keys from the seed.
Opt-in lighter sync gate: when --fast-deploy is set, the deploy attempts to submit as soon as the unshielded sub-wallet is strict-complete and NIGHT > 0, instead of waiting for shielded + dust strict-complete. Intended for preprod wallets whose faucet drop is NIGHT (unshielded) + dust, where unshielded sync completes in seconds but dust strict-complete can take an hour+ on cold caches because dustLedgerEvents is an unfiltered global stream. The risk we accept: if no dust UTXO has materialised in the wallet by the time the deploy submits, the tx is rejected with `Insufficient Funds: could not balance dust` and the user retries without --fast-deploy. The post-sync cache save is skipped in fast-deploy mode to avoid persisting a partial shielded snapshot. Default behaviour is unchanged: gate stays on the strict-complete `state.isSynced` signal that the WalletFacade exposes.
DustBootstrap.register() walks the wallet's unshielded UTXO set, picks out NIGHT UTXOs that don't have registeredForDustGeneration=true yet, and submits a single dust-registration tx via walletFacade.registerNightUtxosForDustGeneration → signRecipe → finalizeRecipe → submitTransaction. Useful for wallets that received NIGHT outside the standard preprod faucet flow (which auto-registers): without registration, those UTXOs never generate dust and the wallet can't pay any tx fees. The fee for the registration tx itself is self-funding — paid from the implicit dust that the NIGHT UTXOs have been accruing since their ctime, computable locally without dust-sync. `packages/deployer/scripts/dust-bootstrap.ts` is a standalone runner (node --experimental-strip-types) that exercises the class against a real network without going through Deployer.prepare. Intended as an operator tool and as smoke-test scaffolding; not exposed via the published CLI.
…stream Lets a fresh-funded wallet bypass the full `dustLedgerEvents(id: 0)` walk (1h+ on preprod) by bootstrapping the dust sub-wallet at an arbitrary `appliedIndex` near the chain tip. Builds a fresh dust wallet, harvests its initial-state JSON snapshot, mutates the `offset` field to N, then restores — the resulting wallet subscribes to `dustLedgerEvents(id: N)` and only walks the tail. Wired through `WalletHandler.build` -> `Deployer.prepare` -> CLI (`compact-deploy --dust-start-id <N>`) and the standalone `dust-bootstrap` script. Implies the dust cache save is skipped post- sync, because the partial-history snapshot would silently poison future runs that resume from it. `scripts/probe-dust-tip.ts` ships as dev-time scaffolding to estimate the current chain tip via a graphql-ws subscription against the preprod indexer. Caveat: only helps wallets whose entire dust event history (initial UTXOs + generation registrations) sits at id >= N. Wallets with historical registrations at id < N will boot with no spendable dust; indexer-side filtered queries (midnight-indexer#1167) are the longer fix.
…t wallets testkit-js's `DEFAULT_DUST_OPTIONS.additionalFeeOverhead` is `5e20` — a fee-balance safety margin sized for production wallets with very large dust reserves. Faucet-funded test wallets on preview/preprod only have ~3e15 dust, so the default makes every deploy fail with `Insufficient Funds: could not balance dust` even when the computed fee is microscopic. Override to `5e14` for all non-mainnet networks (plenty of headroom for the deploy fee, well below a faucet wallet's balance). Also: the `costParameters` config layer is RUNTIME state on `DustWallet(...)`, not baked into the cached snapshot. Our restore path (and the `--dust-start-id` skip-ahead path) were calling `DustWallet(config)` without those parameters, so cached wallets fell back to the SDK's 5e20 default regardless of what we passed in `dustOptions`. Extracted a `buildDustConfig` helper to layer the costParameters on, mirroring what `WalletFactory.createDustWallet` does internally, and applied it everywhere we construct a `DustWallet` builder. Adds `[networks.preview]` to `compact.toml` (preview is the recommended public testnet while preprod is blocked on the `midnight:event[v9]` DustSpendProcessed deserialization bug — see the Midnight dev Discord, May 22). Verified end-to-end by deploying Counter to preview.
…orer URL The --fast-deploy / --dust-start-id flags and DustBootstrap helper were experiments aimed at side-stepping the preprod dust-sync stall. They turned out to be dead ends (the dust commitment tree forbids non-linear inserts, so resuming mid-stream corrupts state), and the preprod blocker lives upstream in wallet-sdk anyway. Strip them along with faucet handling — the deployer now does direct sync-then-deploy. Adds an `explorer` URL field so successful deploys can print a clickable contract link.
Aligns compact-deployer with the SDK lineage that downstream consumers (e.g. OpenZeppelin/midnight-apps) ship via vendor tarballs, so wiring compact-deploy into those projects no longer trips over Symbol-identity mismatches between mixed 3.x/4.x wallet SDKs sharing one process. - midnight-js-*: 4.0.2 -> 4.1.0 - testkit-js: 4.0.2 -> 4.1.0 - compact-js: 2.5.0 -> 2.5.1 - wallet-sdk-shielded: 2.1.0 -> 3.0.0 - wallet-sdk-unshielded-wallet: 2.1.0 -> 3.0.0 - wallet-sdk-facade: 3.0.0 -> 4.0.0 - wallet-sdk-dust-wallet: (new) 4.0.0 - wallet-sdk-hd: 3.0.1 -> 3.0.2 - wallet-sdk-address-format: 3.1.0 -> 3.1.1 Root resolutions pin all midnight-js + testkit-js packages to the local vendor/*-4.1.0.tgz tarballs (matching the existing ledger-v8 pin).
…ocker, roadmap Frame the deployer as developer-preview / testnet-only, document the upstream preprod dust-sync bug, point bug reports at the right repos, and list the known TODOs/roadmap so consumers know what's coming.
One-liner per exported symbol; multi-line only when the body encodes a non-obvious WHY (upstream SDK quirks, hidden invariants, version pins). Drops step-narration, signature-restating prose, tutorial-style "why both / mirrors X" paragraphs, and historical asides that belong in commit messages rather than source. 28 files, -661 net lines (833 deletions, 172 insertions). No behaviour change — types pass clean on both packages and all 168 tests (149 deployer + 19 cli) keep passing. Also drops the stale "multi-contract orchestration" item from the deployer README — Compact composes via modules into one artifact, so there's no deploy graph to orchestrate.
Pinned to 4.0.15 to match the workspace vitest version exactly — the newer 4.1.7 coverage package imports `BaseCoverageProvider` from `vitest/node`, which 4.0.15 doesn't export. Enables `yarn vitest run --coverage` on both deployer + cli.
These four CLI source files had 0% effective coverage — no test file imported them, so v8 didn't even see them in the report. Adds 47 tests across four new spec files mirroring the existing `runCompiler.test.ts` patterns (vi.mock external deps, custom helpers, exit-code assertions via spied process.exit). Coverage on these files after this commit: - logger.ts: 100% stmt / 100% branch / 100% func / 100% line - prompt.ts: 100% stmt / 100% branch / 100% func / 100% line - runBuilder.ts: 100% stmt / 100% branch / 100% func / 100% line - runDeploy.ts: 100% stmt / 100% branch / 100% func / 100% line CLI package aggregate: 100% stmt / 99.13% branch / 100% func / 100% line across 5 files, 66 tests passing.
…e cases Closes the three worst statement-coverage gaps in the deployer package (seeds.ts 52.6%, keystore.ts 68.8%, deployer.ts 74.6%) by exercising error paths and conditional branches the existing happy-path tests skipped. - seeds.test.ts (+14): resolveSeed precedence chain (cli/env/keystore/ local/final-throw), keystore not-found + no-prompt errors, safeRead + absoluteUnder edge cases - keystore.test.ts (+10): decrypt happy path + MAC-mismatch error, AES-CTR round-trip, writeToFile 0o600 mode check, toJSON shape, fromJSON KDF + cipher rejections - deployer.test.ts (+12): syncAndVerifyFunds timeout + UnfundedWallet paths (driven indirectly via prepare with a fake wallet provider), buildExplorerUrl variants (0x-prefix, trailing slash, empty cases), resolveTargets missing-default-network, owned-wallet saveCache failure warn-log, describeProgress branch with highest > 0 Coverage after this commit: - seeds.ts: 52.6% → 100% stmt / 96.55% branch - keystore.ts: 68.8% → 100% stmt / 100% branch - deployer.ts: 74.6% → 98.55% stmt / 92.10% branch Bonus: the 2 pre-existing logWalletAddresses unhandled-rejection warnings are now gone — properly mocking @midnight-ntwrk/wallet-sdk-address-format + getNetworkId lets the owned-wallet happy path exercise without partial-mock leakage. 201 deployer tests passing (was 149).
Brings six deployer files from the 80-95% statement band up to ≥95%
by exercising remaining error / fallback branches:
- init-state.test.ts (+2): module-ref happy path + missing-export ConfigError
- args.test.ts (+3): module-ref happy + non-array ConfigError + file-ref
malformed-JSON ConfigError
- compact-config.test.ts (+3): has/list helpers, --config missing-path
ConfigError, findUpward returning undefined when no compact.toml exists
- artifact.test.ts (+2): witnesses module export not an object
ConfigError + module-ref happy path
- build.test.ts (+1): invoke privateStoragePasswordProvider callback so
the closure on line 59 runs
- private-state-password.test.ts (+1): vi.doMock('node:crypto') forces
the rejection loop to fall through to the explicit throw
Final coverage on these six:
- init-state.ts: 100% / 100%
- args.ts: 100% / 100%
- artifact.ts: 98.07% / 93.33% (line 128 is dead-defensive
— schema makes it unreachable)
- compact-config.ts: 97.87% / 90.47% (line 48 is a TOCTOU
readFile path — out of scope)
- build.ts: 100% / 100%
- private-state-password.ts: 100% / 100%
…erver / handler Three single-branch gaps in otherwise well-covered files: - deployments.test.ts (+1): constructor with absolute deploymentsDir - proof-server.test.ts (+1): static-container stop path for PROOF_SERVER_PORT env precedence - handler.test.ts (+1): saveCache() warn-log path when persist throws Coverage after this commit: - deployments.ts: 96.15% line → 100% line - proof-server.ts: 96.55% line → 100% line - handler.ts: 98.48% line → 100% line
Adds a vitest coverage config to both deployer + cli (deployer didn't have a vitest.config.ts at all; created one). Enforced thresholds: statements: 95, branches: 90, functions: 100, lines: 95 Current state clears all four bars on both packages with headroom: deployer: 99.11/94.75/100/99.27 cli: 100/99.13/100/100 A coverage drop below those thresholds now fails the relevant package's test script — regressions get caught in CI. Wires: - `yarn coverage` per package (`vitest run --coverage`) - root `yarn coverage` (`turbo run coverage`) - coverage task in turbo.json with `coverage/**` as output (already gitignored)
Sweeps the `Phrase A — Phrase B; Phrase C.` em-dash + semicolon AI-tell out of every prose-bearing line in the deployer package. Splits the glued clauses into two or three plain sentences, swaps the em-dash for a parenthesis or colon, or just uses commas. Single em-dashes setting off a real parenthetical aside are kept (e.g. the three `Unshielded sync complete — NIGHT balance: ...` log lines). - README.md: Known issues numbered list (5 items), Status callout, and a handful of other paragraphs. Net -59 lines as the dense paragraphs collapse into shorter sentences. - src/deployer.ts: 6 TSDoc + inline-comment rewrites. - src/wallet/handler.ts: 3 rewrites. - src/loaders/context.ts, src/providers/network.ts, src/wallet/keystore.ts: 1 each. - src/deployer.test.ts: 2 inline-comment rewrites. 201 deployer tests passing, types clean. CLI package untouched (out of scope for this pass).
Drops the root-level `examples/` directory with one runnable example,
`fungible-token/`, that deploys a small ERC20-flavoured contract via
`compact-deploy`. The contract wraps the OpenZeppelin Compact
FungibleToken module (vendored into `contracts/token/`,
`contracts/security/`, `contracts/utils/` — no submodule) and exposes
a deliberately rich constructor so the example shows every common
Compact primitive type flowing from a JS args file into a deployed
contract:
- Opaque<"string"> (name, symbol)
- Uint<8> (decimals)
- Bytes<32> (treasury)
- Uint<128> (maxSupply)
- Uint<32> (feeBps)
- Uint<64> (quorum)
- Boolean (isMintable)
- Bytes<8> (tag)
The example is OUTSIDE the yarn workspace per the design discussion —
self-contained, with its own compact.toml, package.json, and committed
artifact. Scripts call into the workspace CLI via relative paths
(`node ../../packages/cli/dist/runDeploy.js`), so users don't need
`yarn install` inside the example folder.
Also tweaks .gitignore:
- Negate the global `artifacts/` rule for `examples/**/artifacts/`
so committed example artifacts actually ship.
- Change `deploy/*.{seed,signingkey,keystore.json}` to
`**/deploy/*.{...}` so the rule matches nested deploy directories
under examples/.
Adds an optional `args?: readonly unknown[]` field to DeployerOptions so programmatic callers can pass constructor args directly without round-tripping through a JSON file or .args.mjs module. This is the ergonomic path for hand-written deploy scripts that want to keep args inline in JS (BigInts, Uint8Arrays, Booleans). Precedence (highest first): api > --args > inline TOML > file ref > module ref > empty `ConstructorArgs.source` gains a new 'api' variant so callers can tell where the args came from. Adds 2 tests covering apiArgs winning over every other source and an empty-array case. 203 deployer tests passing (was 201), types clean.
…4.1.0)
The Midnight team published the full midnight-js + testkit-js 4.1.0
lineage to the public npm registry on 2026-05-25, so we no longer need
to vendor 12 of the 13 local tarballs in vendor/.
Dropped resolutions for:
midnight-js-{compact,contracts,fetch-zk-config-provider,
http-client-proof-provider,indexer-public-data-provider,
level-private-state-provider,logger-provider,
network-id,node-zk-config-provider,types,utils}
testkit-js
Kept @midnight-ntwrk/midnight-js-protocol as a vendor tarball — it's
still not published on npm (404 from registry as of 2026-05-25).
203 deployer tests still pass on npm-resolved 4.1.0; lockfile changes
are the yarn 4 replacement of 12 file: locators with npm: descriptors.
… deploy script
Reworks the fungible-token example so it shows the actual user
experience: install @openzeppelin/compact-deployer like any npm
dependency, then run a hand-written per-contract deploy script.
What changed:
- `package.json` declares @openzeppelin/compact-deployer as a real
dependency via `file:../../packages/deployer` (the local workspace
copy). The CLI is intentionally NOT a dep — the example uses the
programmatic API instead, which sidesteps the CLI's workspace:^
transitive deps. Only one resolution remains (midnight-js-protocol
still vendored — not yet on npm).
- New `deploy/deployTokenExample.ts` is the per-contract deploy
script. Imports `Deployer` from the installed package, declares the
9 constructor args inline as native JS values (string, number,
bigint, Uint8Array, boolean), parses a small argv (--network,
--dry-run, --sync-timeout), then runs `Deployer.prepare()` +
`.deploy()` / `.dryRun()`. Edit the `constructorArgs` array to
change what gets deployed.
- Dropped `deploy/TokenExample.args.mjs`. The TOML `args = { module }`
reference is gone too — args now flow from the script via the new
`DeployerOptions.args` field added in cb34632.
- `package.json` scripts call `node deploy/deployTokenExample.ts`
directly. Node 24's native TypeScript stripping runs the .ts file
without a build step.
- README rewritten around the install + script flow (the prior version
walked through a `node ../../packages/cli/dist/runDeploy.js`
workaround that's no longer needed).
- .gitignore extended so nested `.yarn/cache`, `.yarn/install-state.gz`,
and `.pnp.*` from per-example installs don't ship.
Verified: `yarn install` in examples/fungible-token resolves cleanly
(373 packages, ~80MB) and the script reaches `Deployer.prepare()`
through to the expected "no signing key" error.
Surface a high-level entrypoint so hand-written deploy scripts can be
as short as:
import { runDeploy } from '@openzeppelin/compact-deployer';
await runDeploy({ contract: 'Token', args: [...] });
runDeploy() handles argv parsing (--network, --dry-run, --sync-timeout,
--no-cache, --seed-file, --proof-server, --config, --json, -v), pino
logger setup, Deployer.prepare + deploy/dryRun, formatted result
output, and process.exit with the typed DeployError.exitCode on
failure. Explicit options always win over argv.
This is the programmatic mirror of the `compact-deploy` CLI binary
that the `packages/cli/src/runDeploy.ts` shell uses, minus the
CLI-only concerns (ora, chalk, --help, prompts). Lets consumers embed
the deploy flow in their own scripts without reimplementing argv +
exit-code routing.
7 new tests for runDeploy cover argv merging, --sync-timeout
validation, JSON output, and DeployError vs generic-error exit-code
mapping. 210 deployer tests passing.
… flows
Treats the example as a real yarn workspace member instead of a
file:-installed standalone. Three knock-on changes:
- `examples/*` is added to the root `workspaces` array. The example's
package.json depends on `@openzeppelin/compact-deployer` and
`@openzeppelin/compact-cli` via `workspace:^`. The midnight-js-protocol
vendor-tarball resolution lives only in the root package.json now —
the example inherits it via the workspace.
- The compile script switches from the system `compact` binary to the
workspace's own `compact-compiler` (hoisted into the example's bin
via the workspace `node_modules/.bin/` chain).
- The deploy scripts shrink to ~5 lines each by calling the new
`runDeploy()` helper from the deployer. The boilerplate (argv,
logger, exit codes) moves out of the user's script.
Two flows are now demonstrated side-by-side:
- `deployTokenExample.ts` — args inline in the script (recommended
for rich JS types).
- `TokenExample.args.mjs` + `deployTokenExampleFromArgs.ts` — args in
a separate module referenced from compact.toml.
Verified: `cd examples/fungible-token && yarn compile && yarn deploy:local`
reaches `Deployer.prepare()` and fails at the expected missing-signingkey
step.
`compact-compiler --hierarchical` produces an artifact directory for every .compact file in the example tree, including the vendored modules (FungibleToken, Initializable, Utils). The module dirs have no keys/zkir and aren't deployable on their own — they regenerate on every `yarn compile` and just add diff noise. Ignore them so only `artifacts/TokenExample/` stays committed.
…+ add preprod
Reorganises the fungible-token example around two clean paths:
Path 1 — `yarn deploy:{local,preview,preprod}` runs the TS script
(`deploy/deployTokenExample.ts`) with args inline. Built on
`runDeploy()` from @openzeppelin/compact-deployer.
Path 2 — `yarn cli:{local,preview,preprod}` runs the `compact-deploy`
binary from @openzeppelin/compact-cli. Args come from
`deploy/TokenExample.args.mjs` via the `args = { module }`
ref in `compact.toml`. No JS script needed.
Other changes:
- Adds a `[networks.preprod]` block to `compact.toml`.
- Drops `deployTokenExampleFromArgs.ts` — its role (programmatic API
reading args from the module) is now covered by the CLI demo.
- README rewritten around the two paths + a "when to pick which" table.
Three new ways to pass constructor args to runDeploy, all derived
from the artifact's compiler-emitted .d.ts — no codegen, no
hand-written interface unless the named-object form is chosen:
- Curried call: `runDeploy(Contract)(...args)`. Names the contract
once via the imported class; resolveContractName walks
`[contracts.X]` entries in compact.toml and identity-matches the
Contract class to pick the config. Args are typed function
parameters, so the editor shows each param's name + type via
signature help as you type.
- Tuple form via `ConstructorArgsOf<typeof Contract>`: extracts the
labeled tuple from `Contract.prototype.initialState` for callers
who prefer the options-object API. Hover shows types; no
per-comma signature help.
- Named-object form: `args: { _name: '…', … }`. The deployer parses
the artifact's `index.d.ts` at runtime (parseConstructorParamNames,
with SSA-suffix stripping) and reorders the object into the
positional tuple. Requires a hand-written interface today; see
`logs/feature-compactc-export-constructor-args-interface.md` for
the upstream fix that would generate it.
Also exports `constructorArgs(Contract, ...args)`, a typed helper
that gives per-comma editor hints inside the options-object call
form.
The deploy script now uses `runDeploy(Contract)(...args)` so the contract is named once via the imported `Contract` class — the deployer matches it to `[contracts.TokenExample]` by class identity. Each constructor arg is a typed function parameter, so the editor shows the next param's name + type as you type each comma. README documents the curried form as the primary path with the options-object form (string-keyed) listed as the alternate for multi-contract / TOML-driven / programmatic flows. TokenExample.args.mjs uses `bigint` literals consistently (Compact emits every Uint<N> as bigint).
Types of changes
What types of changes does your code introduce to OpenZeppelin Midnight Contracts?
Put an
xin the boxes that applyFixes #???
PR Checklist
Further comments
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...