Skip to content

Pswap Tracking [WIP] - #3

Open
VAIBHAVJINDAL3012 wants to merge 77 commits into
nextfrom
feat/pswap-tracking
Open

Pswap Tracking [WIP]#3
VAIBHAVJINDAL3012 wants to merge 77 commits into
nextfrom
feat/pswap-tracking

Conversation

@VAIBHAVJINDAL3012

Copy link
Copy Markdown

Summary

Adds PSWAP lineage tracking to the web SDK: the client now persists the lifecycle of partially-fillable swap (PSWAP) notes across fill rounds and exposes that state to JavaScript and React consumers. This is the browser-side counterpart to the Rust client tracking work.

Client PR: 0xMiden/rust-sdk#2231

This builds on the PSWAP transactions feature already in the base branch (feat/migrate-client-2214); this PR adds the tracking + read surface on top of it.

What's included

idxdb-store (persistence)

  • New pswapLineages object store (schema v2) tracking each order's original note, current tip, depth, remaining offered/requested amounts, lifecycle state, and create/update block heights.
  • New NoteTagSource::Subscription so the asset-pair subscription tag is registered on lineage creation and dropped when a lineage reaches a terminal state.
  • Store-trait pswap methods (upsert / get / list / apply_pswap_round) implemented to match the SQLite backend: monotonic-depth guard, unknown-order rejection, terminal tip-freeze, and atomic round application that reconstructs payback + remainder notes into the input-note set (INSERT-OR-IGNORE), making private paybacks consumable.

web-client (WASM/JS API)

  • New PswapLineageRecord model and four WebClient methods: getPswapLineages, getPswapLineagesFor(account), getPswapLineage(orderId), and buildPswapCancelByOrder(orderId).
  • Updated api-types.d.ts and resource wiring.

react-sdk (hooks)

  • usePswapLineages, usePswapLineagesFor, usePswapLineage, and usePswapCancelByOrder, with exported types.

…den#14)

* docs: import planning docs from miden-client (web SDK ones)

Per @SantiagoPittella's review on miden-client #1992: 5 of the 13
stray planning .md files at the miden-client repo root are web-SDK-
relevant — they belong here, not in the Rust client repo. Importing
under docs/planning/ with an index README that calls out their
snapshot-not-maintained status.

  AGENTS.md
  Api.Rework.Impl.md
  REACT_SDK_PLAN.md
  SimplifiedAPI.md
  SimplifiedAPI.sdk.review.md

The miden-client side (#1992 commit e20e6261a) deletes all 13 stray
docs; the other 8 are either already-tracked issues, stale, or non-
client concerns and don't need to be ported.

* docs: keep only AGENTS.md from imported planning docs

* ci: replace paths-ignore with intra-job filter so docs-only PRs aren't blocked by required checks that never run
…iden#19)

* chore: add .nvmrc + lefthook + lint-staged for local dev hygiene

* chore: fix lefthook config — drop broken per-file tsc, use pnpm exec, guard prepare
…iden#18)

* chore(web-client): swap chai expect for @playwright/test expect

* chore(web-client): swap puppeteer Page type for @playwright/test

* chore(web-client): drop mocha/chai/puppeteer/esm/ts-node devDeps + .mocharc.json

The legacy mocha-based Playwright wiring has been fully replaced by
@playwright/test (assertions) and vitest (unit tests). Remove the dead
test infrastructure:

- crates/web-client/.mocharc.json (deleted)
- crates/web-client/tsconfig.json: drop the "ts-node" block
- crates/web-client/package.json devDependencies removed:
  - mocha
  - chai
  - puppeteer
  - esm
  - ts-node
- pnpm-lock.yaml regenerated

Verified: pnpm --filter @miden-sdk/miden-sdk run test:unit passes
(295 tests across 13 files).
* chore(deps): bump miden-client to 0.14.5

Bumps the workspace miden-client dependency on `main` from 0.14.0 (locked
at 0.14.4) to 0.14.5, the latest 0.14.x patch on crates.io. No API
changes required on the web-sdk side — same minor line, just patch-level
updates from upstream miden-client.

The `next` branch separately tracks miden-client `next` (0.15.0
unreleased) and is unaffected by this bump.

* docs: clarify lazy entry — SSR / controlled WASM init, not 'most users skip crypto'

The previous README framing of `@miden-sdk/miden-sdk/lazy` as for 'apps
where most users never touch crypto' is misleading. The actual point of
the lazy entry is to allow usage in environments that hang on top-level
await (Next.js / SSR, Capacitor WKWebView) or where the caller wants to
explicitly control when the WASM-init cost is paid.

Also adds the missing `await MidenClient.ready()` contract: until you
call it, every wasm-bindgen type imported from the lazy entry is just a
stub that throws on construction. Async SDK methods await internally
and are exempt.

* docs: add React-side lazy guidance — gate UI on isReady from useMiden()

Apps using @miden-sdk/react with the lazy entry don't call
MidenClient.ready() directly; MidenProvider does it for them and
exposes the readiness state through useMiden() as { isReady,
isInitializing, error }. Adds the contract + a minimal example,
plus pointers to the loadingComponent / errorComponent provider
props for the zero-glue case.
* chore(ci): add publint + arethetypeswrong gates for published packages

Adds publint and @arethetypeswrong/cli as root devDeps and wires three
new scripts:
- check:publint   - runs publint per published workspace package
- check:attw      - runs attw --pack . per published workspace package
- check:publish   - builds web-client, react-sdk, vite-plugin then runs
                    both gates

Filters target the three publishable packages by name
(@miden-sdk/miden-sdk, @miden-sdk/react, @miden-sdk/vite-plugin), which
skips the private web_store workspace member that has no exports map
worth checking.

A new workflow (.github/workflows/check-publish.yml) runs check:publish
on PRs and pushes to main/next. It mirrors the WASM build setup from
test.yml's build-web-client-dist-folder job (Rust toolchain, sccache,
binaryen, Swatinem/rust-cache) and uses MIDEN_FAST_BUILD on the WASM
build since publint/attw inspect package shape and types, not the
optimization level of the WASM blob.

Mode: STRICT. Per the task rule (document only if both tools find > ~3
issues per package; otherwise leave strict), only @miden-sdk/miden-sdk
clearly clears that bar - the other two packages have <= 1 attw issue
and 1 publint warning each, so a doc-and-tolerate path would be
overkill. The CI gate will be red on first PR; the author should decide
whether to fix the underlying export map issues or merge red.

Local results today (built with MIDEN_FAST_BUILD=true):

publint (exit 0 - warnings/suggestions only):
- @miden-sdk/miden-sdk:    1 suggestion  (pkg.browser refactor)
- @miden-sdk/react:        1 warning + 2 suggestions
                           (types ambiguous under "import" condition;
                            missing "type" field; repository.url shape)
- @miden-sdk/vite-plugin:  1 warning + 2 suggestions (same shape as react)

attw (exit 1 - real failures):
- @miden-sdk/miden-sdk:    CJSResolvesToESM, InternalResolutionError
                           across multiple matrix cells, NoResolution
                           on the /lazy subpath under node10
- @miden-sdk/react:        FalseCJS (masquerading-as-CJS) under node16
                           from-ESM for both . and /lazy; NoResolution
                           on /lazy under node10
- @miden-sdk/vite-plugin:  FalseCJS under node16 from-ESM

Per task scope, no source or build-config changes were made to address
these - the gate ships first, the fixes are followup work.

* chore(miden-sdk): fix exports map for attw + publint compliance

- Split the `exports` conditions into explicit `import` blocks with
  `types` listed first, eliminating the ambiguous-types warning publint
  reported. Drop the implicit `default` fallthrough that caused
  attw to flag CJSResolvesToESM under the node16-cjs profile.
- Add an `.attw.json` selecting the `esm-only` profile. The package is
  `"type": "module"` and ships only ESM artifacts (rollup output +
  WASM glue); `require()` consumers must already use a dynamic import.
  The `esm-only` profile communicates this intent to attw and makes
  the node10 / node16-cjs columns informational only.
- Add a post-build step (`scripts/post-build.js`) that:
    1. Rewrites extensionless relative specifiers in the published
       `dist/*.d.ts` files (e.g. `from "./api-types"` ->
       `from "./api-types.js"`). Without explicit extensions, Node16
       type resolution flags an `InternalResolutionError` on every
       relative import inside `dist/index.d.ts` and `dist/api-types.d.ts`.
    2. Emits a `lazy/package.json` shim at the package root pointing at
       `dist/index.{js,d.ts}` so node10 resolution (which doesn't read
       `exports`) can still locate `@miden-sdk/miden-sdk/lazy`.
- Add `lazy` to the `files` array so the shim ships in the tarball.
- Expose `./package.json` from `exports` to keep tooling that reads
  the manifest at runtime working under strict resolution.

Public import paths (`@miden-sdk/miden-sdk` and
`@miden-sdk/miden-sdk/lazy`) are unchanged. Verified that the
existing vitest unit suite (295 tests) still passes against the new
build output.

* chore(react-sdk): fix exports map for attw + publint compliance

- Split each `exports` subpath into explicit `import` / `require`
  conditions with `types` listed first. publint flagged the previous
  shape because `types: "./dist/index.d.ts"` resolved as CJS under the
  `import` condition (FalseCJS / ambiguous-types). The new shape uses
  the `.d.mts` declaration tsup already emits for the ESM build, so
  TypeScript sees an `.mts` declaration when resolving via `import`
  and a `.d.ts` declaration when resolving via `require`.
- Add `"type": "commonjs"` to silence publint's package-type-detection
  suggestion (tsup emits `.js` as CJS and `.mjs` as ESM, matching).
- Add a `lazy/package.json` shim at the package root that points at
  `dist/lazy.{js,mjs,d.ts}`. node10 resolution doesn't read the
  `exports` map, so `@miden-sdk/react/lazy` previously failed to
  resolve under that column; the shim is the standard fix.
- Expose `./package.json` from `exports` and add `lazy` to the `files`
  array so the shim ships in the tarball.

Public import paths (`@miden-sdk/react` and `@miden-sdk/react/lazy`)
are unchanged. Verified that the existing vitest unit suite (719
tests) still passes.

* chore(vite-plugin): fix exports map for attw + publint compliance

- Split the `exports["."]` block into explicit `import` / `require`
  conditions with `types` listed first. publint flagged the previous
  shape because `types: "./dist/index.d.ts"` was interpreted as CJS
  when resolving via the `import` condition (FalseCJS / ambiguous
  types under `import`). The new shape uses the `.d.mts` declaration
  tsup already emits for the ESM build, so TypeScript sees an
  `.mts` declaration when resolving via `import` and a `.d.ts`
  declaration when resolving via `require`.
- Add `"type": "commonjs"` to silence publint's package-type-detection
  suggestion (tsup emits `.js` as CJS and `.mjs` as ESM, matching).

Public import path (`@miden-sdk/vite-plugin`) is unchanged. Verified
the existing vitest suite (27 tests) still passes.

* chore(react-sdk): drop CJS output, ship ESM-only

* chore(web-client): deduplicate lazy/package.json shim

* chore(ci): prettier-format post-build.js

* chore(react-sdk): rename CJS configs to .cjs after ESM-only flip

Switching @miden-sdk/react to "type": "module" makes .js files ESM by
default, which breaks two CJS-syntax files:
- packages/react-sdk/eslint.config.js (uses module.exports + require())
- packages/react-sdk/test/serve-tests.js (uses require())

Renamed both to .cjs. Updated playwright.config.ts:34 to reference the
new filename. ESLint auto-discovers either extension; no other consumer
references the renamed paths.
* chore: add vitest workspace + root test script

* chore(vitest): migrate from defineWorkspace to defineConfig projects (vitest 3 modern shape)

* chore(ci): exclude root vitest.config.ts from eslint typed-linting
…n#17)

* chore: add knip for unused-exports/deps detection

Lands knip 6.7.0 plus a baseline knip.jsonc config covering the four
TS-bearing workspaces (`packages/react-sdk`, `packages/vite-plugin`,
`crates/web-client`, `crates/idxdb-store/src`) and a root scripts entry.

Mode: warning-only. The script is `knip --no-exit-code` so CI does not
go red on day-one findings; promoting to strict mode is a follow-up
once the baseline backlog is cleared.

Baseline findings (pnpm run check:knip, exit 0 in --no-exit-code):

  Unused files          4   (3 are .d.ts ambient declarations in
                              crates/web-client/js/types/, plus
                              packages/react-sdk/src/__tests__/utils/test-utils.tsx)
  Unused dependencies   3   (root prettier; web-client @rollup/plugin-typescript, dexie)
  Unused devDeps        5   (root and idxdb-store @typescript-eslint/eslint-plugin;
                              web-client http-server + mocha;
                              react-sdk http-server)
  Unlisted dependency   1   (web-client test references @aspect-build/aspect-rsdoctor)
  Unlisted binary       1   (vite in .github/workflows/wallet-pages.yml)
  Unresolved imports    4   (web-client tests import ./eager.js / ./index.js
                              relative paths — likely dist-time URLs)
  Unused exports       23   (all in packages/react-sdk test mocks)
  Unused exported types 9   (react-sdk SignerContext + types/index.ts re-exports)
  Duplicate exports     3   (playwright.global.setup.ts; react-jsx-runtime.js;
                              vite-plugin index.ts midenVitePlugin|default)

Per-workspace tally:
  root                       1 dep, 1 devDep
  packages/react-sdk         1 file, 1 devDep, 23 exports, 9 types, 1 dup-export
  packages/vite-plugin       1 dup-export
  crates/web-client          3 files, 2 deps, 2 devDeps, 1 unlisted dep,
                             1 unlisted bin, 4 unresolved imports, 1 dup-export
  crates/idxdb-store/src     1 devDep

No CI workflow added in this commit — that lands separately once the
baseline is cleaned up. No source code modified.

* chore(web-client): drop dead @aspect-build import in sync_lock test

The 'waiters are rejected when sync times out' test destructured
acquireSyncLock/releaseSyncLock/releaseSyncLockWithError from a stale
@aspect-build/aspect-rsdoctor path that has nothing to do with this
codebase, then immediately fell back to nulls and never used the
bindings. The only thing actually exercised below is client.syncState(),
so the destructuring + bogus import was pure dead code.

Flagged by knip as the sole 'unlisted dependency' import in
crates/web-client; removing the dead block both fixes the finding and
makes the test honest about what it's checking.

* chore: drop unused devDeps + deps flagged by knip

Removes packages that no source file, build config, lint config, test, or
script references. Verified each by grepping the repo for imports / CLI
invocations / config references before deletion.

- root: @typescript-eslint/eslint-plugin (root eslint.config.js wires
  parser only; no plugin-rules block, so the plugin pkg sits idle)
- crates/idxdb-store/src: @typescript-eslint/eslint-plugin (its
  eslint.config.mjs uses 'typescript-eslint' meta-package, not the
  legacy plugin)
- packages/react-sdk: http-server (no script or workflow runs it)
- crates/web-client: http-server, mocha (rollup + playwright + vitest
  pipeline does not invoke either; no .mocharc, no http-server script)
- crates/web-client deps: @rollup/plugin-typescript (rollup.config.js
  uses node-resolve + commonjs + wasm-tool only — no TS plugin), dexie
  (only the idxdb-store crate uses dexie, and it lists its own copy)

Lockfile regenerated via pnpm install --no-frozen-lockfile.

* chore: drop unused files + dead exports flagged by knip

react-sdk:
- Delete src/__tests__/utils/test-utils.tsx — exports renderWithProvider,
  renderHookWithProvider, etc., none of which any test imports. The repo
  uses renderHook/render directly from @testing-library/react.
- Strip 'export' off internal mock helpers and types in __tests__/mocks/
  (createMockOutputNote, createMockTransactionRecord, MockNoteFilter and
  ~17 other Mock* constants, MockWebClientType, createMockSignCallback,
  createMockAccountStorageMode). They were never imported across module
  boundaries; making them module-private is correct.
- Delete the createMockSdkModule factory along with the Mock* class /
  enum constants that only existed to populate it. No test in the repo
  ever called the factory, so it pulled an entire wing of dead code.
- Remove unreachable type re-exports: GetKeyCallback / InsertKeyCallback
  / NoteId / NoteVisibility / StorageMode were re-exported through
  src/types/index.ts but src/index.ts never re-exported them, so they
  were not part of the package's public surface. The underlying types
  stay locally-typed where they're actually used.
- Make ClientWithTransactions in src/utils/transactions.ts module-private
  (the noteFilters.ts copy is the one consumers use).

web-client:
- Delete crates/web-client/.mocharc.json (only consumer was mocha, which
  was removed in the previous commit) and the matching ts-node /
  esm devDeps + the now-orphan ts-node block in tsconfig.json.

Public API of @miden-sdk/react is unchanged: every type still exported
through src/index.ts continues to be exported. Only types that were
never reachable through the package entry have been demoted.

* chore(knip): allowlist public-API exports + ambient .d.ts files

After deleting actual dead code, the remaining findings are all cases
where knip can't statically see the consumer:

- crates/web-client/js/types/{index,api-types,docs-entry}.d.ts: shipped
  as the package's published types via 'cpr js/types dist' in the build
  script, so they ARE the consumer of themselves at publish time.
  Registered as entry points with a comment naming the post-build copy
  step.
- ./eager.js, ./index.js: dynamic imports inside page.evaluate()
  callbacks, executed in the browser against http://localhost:8080
  (i.e. the dist/ output served by the test http server). Resolved at
  test runtime, not relative to the Playwright test file. Allowlisted
  via ignoreUnresolved with a comment.
- ./crates/miden_client_web: wasm-bindgen module emitted into dist/ by
  the rollup rust plugin; the .d.ts files reference it but it doesn't
  exist until after build. Same allowlist with a comment.
- prettier: invoked from the repo Makefile via 'pnpm exec prettier .';
  knip doesn't scan Makefile rules. Allowlisted in ignoreDependencies.
- vite: invoked from .github/workflows/wallet-pages.yml via 'pnpm exec
  vite build' inside the wallet example workspace, which has its own
  package.json + lockfile not visible to this monorepo's graph.
  Allowlisted in ignoreBinaries.
- Three intentional dual-export sites (vite plugin named+default, the
  Playwright test fixture's named+default 'test', and the React JSX
  runtime shim's jsx/jsxs/jsxDEV aliases): rules.duplicates set to
  'off' globally with a comment enumerating each case so a future
  change knows when to revisit it.

Also dropped the redundant entry patterns and the empty docs/** ignore
patterns that knip flagged as configuration hints.

After this, 'pnpm exec knip' exits 0 with zero findings; the 3 remaining
'configuration hints' are about react-sdk's package.json exports
pointing at not-yet-built dist/lazy.* files, which is correct and
doesn't affect the exit code.

* chore(knip): flip check:knip to strict mode

All baseline findings have been triaged in the preceding commits, so the
warning-only flag is no longer needed. Knip now fails CI on any new
unused export / dep / file / unresolved import.

* chore(ci): wire knip into lint workflow

* chore(ci): restore dexie + prettier-format knip files

Knip's static scan flagged dexie as unused, but it's bundled into the
web-client test page at runtime via rollup — page.evaluate blocks load
the bundle from localhost:8080 which transitively imports dexie. The
removal broke 22 integration tests with "Failed to resolve module
specifier 'dexie'". Re-added dexie@^4.0.1 to crates/web-client deps
and registered it in knip's top-level ignoreDependencies with a comment.

Also runs prettier --write on knip.jsonc and two react-sdk source
files that were missed in the earlier cleanup pass.

* chore(knip): allowlist publint/attw + .cjs serve-tests rename
…den#20)

Removes overlapping formatting rules (semi, comma-dangle, eol-last,
space-before-blocks, keyword-spacing, no-multiple-empty-lines) from the
root eslint.config.js so Prettier 3.x is the single source of truth for
style. Logic rules (camelcase, @typescript-eslint/no-unused-vars) are
preserved.

Adds eslint-config-prettier as the last entry in both flat configs
(eslint.config.js and packages/react-sdk/eslint.config.js) to disable
any remaining stylistic rules pulled in transitively from rule presets,
preventing eslint and prettier from fighting.

Extends .prettierignore to mirror the eslint configs' ignore list (dist,
target, node_modules, generated .d.ts, docs, idxdb-store codegen) so
prettier --check stays scoped to source we actually own. The existing
.prettierrc.json (trailingComma: es5) is left as-is.

Verified: pnpm --filter @miden-sdk/react run lint shows 0 errors (same
5 pre-existing warnings as main); prettier --check on packages/react-sdk
and packages/vite-plugin source passes with no reformatting needed.
…0xMiden#35)

The pre-split miden-client release workflow shipped all three packages on
a tagged release; the web-sdk port dropped react-sdk and vite-plugin
publishing while keeping web-client. End users `npm install
@miden-sdk/react` were therefore stuck on the `next` dist-tag (set by
publish-web-client-next.yml) instead of getting the latest tagged release.

This restores parity with pre-split behaviour:

- New scripts/check-react-sdk-version-release.sh and
  scripts/check-vite-plugin-version-release.sh — same pattern as the
  existing check-web-client-version-release.sh (compare package.json
  version against the tagged commit's parent, set should_publish flag).

- publish-web-client-release.yml now runs each package's version-bump
  check and conditionally builds + publishes that package. The three
  publishes are independent, so a release that bumps only one package
  republishes only that one.

- The web-client build runs whenever EITHER web-client OR react-sdk needs
  to ship, since react-sdk's build consumes the WASM dist.
Match the secret name to the cargo env var (and to miden-node's
convention) so the publish-crates workflow picks up the org-managed
secret without an indirection.
…ale AGENTS (0xMiden#38)

- Add a top-level CLAUDE.md aimed at AI agents (and humans skimming
  for build/lint/test/release conventions). Captures pnpm-only rule,
  the Makefile-driven workflows, runExclusive, the eager/lazy entry
  contract, the npm-registry-driven publish gate, and cross-repo
  coordination notes. The README already links to it (line 337).
- Refresh packages/react-sdk/CLAUDE.md against the current source:
    * useNotes returns notes/consumableNotes (not input/consumable)
    * useAccounts returns accounts/wallets/faucets (no 'all')
    * Mutation hooks expose action-named callbacks (send, mint, ...)
      and a 'result' field — not generic { mutate, data }
    * useSend / useMultiSend take assetId / recipients (not faucetId
      / outputs)
    * SendResult has txId (not transactionId)
    * SignerAccountConfig uses publicKeyCommitment + accountType
    * Hook reference table reorganized into query/mutation buckets
      and includes the previously-undocumented hooks (useNoteStream,
      useSyncControl, useTransactionHistory, useImportNote / Export*,
      etc.)
- Remove docs/planning/AGENTS.md — 7 lines of miden-client leftover
  that referenced 'yarn prettier' (the repo migrated to pnpm). All
  the still-useful content is now in the top-level CLAUDE.md.
…iden#41)

* ci(changelog): add changelog gate + filtered web-sdk CHANGELOG.md

Mirror miden-client's changelog gate. Each PR must touch one of the
three changelog files; trivial PRs override with the 'no changelog'
label.

Files:
- CHANGELOG.md (new) — pre-filled with miden-client's [web]-tagged
  entries up through 0.14.x. Source-of-truth changelog for the WASM
  client (@miden-sdk/miden-sdk).
- packages/vite-plugin/CHANGELOG.md (new, stub) — placeholder so the
  CI script has a real path to diff against.
- packages/react-sdk/CHANGELOG.md (already exists) — unchanged here.
- scripts/check-changelog.sh (new) — mirrors miden-client's script,
  extended with a third path for the vite-plugin.
- .github/workflows/changelog.yml (new) — fires on PR open / sync /
  label changes.

* ci(changelog): collapse to single root CHANGELOG.md + auto-populate release notes

- Drop packages/react-sdk/CHANGELOG.md and the packages/vite-plugin/
  CHANGELOG.md stub. The single root CHANGELOG.md is the source of
  truth for every published artifact in this repo.
- Simplify scripts/check-changelog.sh accordingly: only the root file
  is checked. The 'no changelog' label still overrides.
- Add scripts/extract-changelog-section.sh: takes a version (with or
  without a leading 'v'), prints the matching '## <version> (...)'
  section body to stdout. Used by the release-notes workflow.
- Add .github/workflows/release-notes.yml: on release:published, looks
  up the matching CHANGELOG section and replaces the release body via
  'gh release edit --notes'. Logs a warning + leaves the body untouched
  if no matching section is found (no failure — release shouldn't be
  blocked on a stale changelog; the gate at PR time is where that's
  enforced).
* ci: migrate heavy jobs to WarpBuild for ~3x speedup

* chore: ignore docs/superpowers

* chore: remove tracked docs/superpowers (now gitignored)
0xMiden#46)

* fix(ci): wait for node-builder/prover gRPC port instead of sleep+pgrep

Root-cause fix for the recurring 'Integration tests (ci-shard-N)'
flake that intermittently surfaces as 'TypeError: Failed to fetch' from
the in-browser gRPC client during ensure_genesis_in_place.

The previous startup pattern was:
  ./bin/testing-node-builder &
  sleep 4
  pgrep -f testing-node-builder || exit 1
which only checked process liveness, not that the gRPC listener had
bound to its TCP port. On slower CI runners, the test job would race
the listener and fail the very first RPC call (get_block_header_by_number)
with a JS-level fetch failure.

Replaced with a /dev/tcp probe loop with a 30s timeout. Same fix
applied to all three startup sites:
- integration-tests-web-client: testing-node-builder on :57291
- integration-tests-remote-prover-web-client: testing-node-builder on :57291
- integration-tests-remote-prover-web-client: testing-remote-prover on :50051

If the port isn't reachable in 30s the step still fails with a clear
error (and prints whether the process is running at all).

* fix(ci): wait for node-builder/prover stdout readiness signal, not bare TCP

Previous fix (b02c1dc) replaced sleep+pgrep with a TCP-probe loop on
:57291. The probe consistently passes within 6s on WarpBuild runners,
but tests STILL hit 'TypeError: Failed to fetch' from the in-browser
gRPC client.

Root cause: the node-builder binds its public TCP listener early but
doesn't finish wiring up the gRPC services (store, ntx-builder, block-
producer, validator) until `NodeBuilder::start().await` returns.
A bare TCP probe to :57291 succeeds before the gRPC service is
registered — in-browser fetches race in and fail.

The reliable readiness signal is the explicit
'Node started successfully with PID' stdout line printed AFTER
start().await returns (see miden-client
crates/testing/node-builder/src/main.rs:55).

This commit:
- Pipes node-builder stdout to /tmp/node-builder.log and waits
  (timeout 60s) for the readiness line to appear before returning
  from the step.
- Same treatment for the remote-prover step ('Remote prover
  listening on <addr>' from prover/src/main.rs:87).
- On timeout, dumps the last 50 lines of the captured log so the
  failure mode is visible.

* fix(ci): force line-buffered stdout via stdbuf -oL on test backends

Addendum to 931e36a. The stdout-grep readiness check on the captured
log was timing out because Rust's stdout is BLOCK-buffered when
connected to a file (`>/tmp/node-builder.log`) instead of
line-buffered when connected to a TTY. The 'Created data directory'
line happened to flush, but 'Node started successfully' (printed
~80 bytes later) never reached the buffer fill threshold (~8KB) so
it stayed in memory and the grep loop timed out at 60s.

Wrap both backend launches with `stdbuf -oL` (coreutils) to force
line-buffered stdout, which makes println output visible to the
log file as soon as the newline is written. Same fix on the remote
prover step.

* fix(ci): revert readiness probe over-engineering, add Playwright retries: 1

Backing out 931e36a + a31c792. The root-cause hypothesis (gRPC
listener bound before service registered) led to a stdout-grep
readiness check that consistently TIMED OUT — start().await either
hangs or stdout doesn't flush properly when redirected to a file
(stdbuf -oL didn't fix it). All 4 integration shards failed solid
on the over-engineered version.

Restore the original `sleep 4 && pgrep` startup pattern (which was
flaky at maybe 1-3% of runs but functional 97% of the time), and
add `retries: 1` to playwright.config.ts on CI to mask the residual
flake without doubling test wall-clock.

The actual root cause of the original 'TypeError: Failed to fetch'
flake remains unidentified — not enough signal in failed runs to
isolate it. If it becomes a real problem (>5% of runs), revisit
with debug instrumentation in the test harness or the node-builder
binary itself rather than the workflow YAML.
…iden#48)

* ci(publish): consolidate to single workflow + trusted publishing (main)

Replaces the two-workflow setup (publish-web-client-release.yml +
publish-web-client-next.yml) with a single publish-web-sdk.yml that
handles both the GitHub-release event AND the next-channel patch-release
PR-merge event in one file.

Why one file: npm's trusted publishing has a (package, repo) uniqueness
constraint — only ONE workflow filename per package can be the trusted
publisher. Probed empirically: registering trust for a second workflow
on the same package returns 409 Conflict, including with a different
`--env` scope. To use trusted publishing for both flows, both must
come from the same workflow file.

Migration to trusted publishing:
- permissions.id-token: write at workflow + publish-job level → GitHub
  mints the OIDC token npm exchanges for a short-lived publish credential.
- node-version bumped to 24 + force-update of npm CLI to ensure ≥ 11.5
  (required for OIDC support).
- NPM_WEBCLIENT_TOKEN env vars dropped from every publish step.
- --provenance flag added to every `npm publish` / `pnpm publish`
  for the signed attestation that ships with the package.

Branching by event:
- release:published → `--tag latest`, gated by check-*-version-release.sh.
- pull_request:closed (against next, with patch-release label) → `--tag
  next`, gated by check-*-version-pr.sh. Inert on main (branch filter
  excludes main-targeted PRs); kept here for parity so the same trust
  config works across both long-lived branches.
- vite-plugin still ships only on release events (no pr.sh script for
  it). Tracked as future work.

Trust registration follow-up: each of the 6 published packages has its
trust pointing at `publish-web-client-release.yml`. After this lands,
huna1 (npm org owner) revokes those + re-registers against
`publish-web-sdk.yml` for both branches' workflow location. Until that
happens the publish job will fail at the publish step (no trust →
401).

* fix(ci/publish): guard skipped job + pin npm to ^11.5

Two review-feedback fixes on the consolidated publish workflow:

1. `skipped:` job no longer fires misleadingly when `check-version`
   itself was skipped. Previously: a PR merged to `next` without the
   `patch release` label rejected at `check-version`'s `if:`, all
   `should_publish_*` outputs were empty strings, the `!= 'true'`
   checks all evaluated true, and `skipped:` ran printing
   "All publish gates failed" — even though no gating ever happened.
   Adding `needs.check-version.result == 'success'` to the guard
   prevents that.

2. Pin the global npm install to `^11.5` instead of `@latest`. A
   future npm 12 with breaking changes to `--provenance` / OIDC
   semantics would silently break this workflow under @latest. The
   ^11.5 floor still gets the OIDC support we need.
)

* ci: auto-cut GitHub release on 'patch release' merge to main

* docs: changelog entry for auto-release workflow
…ain) (0xMiden#53)

* fix: restore exec bit on check-react-sdk-version-pr.sh

* fix(ci): wait for node-builder readiness via pty (real fix)

Spawn testing-node-builder + testing-remote-prover via 'script -qfec'
(allocates a pty), then poll the captured log for the binary's own
'Node started successfully' / 'Remote prover listening' stdout signal.

Why each prior approach failed:
- 'sleep 4 && pgrep': only proves the process is alive, not that the
  port is bound or gRPC handlers are registered. ~10% flake.
- TCP probe (/dev/tcp): kernel-level handshake succeeds the moment
  TcpListener::bind() is called, but tonic's gRPC service dispatcher
  registers AFTER that. Racy.
- 'stdbuf -oL': only modifies libc stdio buffering. Rust's println!()
  goes through std::io::stdout()'s own BufWriter (~8KB block buffer
  on non-tty fds) and ignores stdbuf entirely. Captured log stayed
  empty until the buffer filled, long past the readiness window.

The pty is the actual fix: Rust detects the tty fd and constructs
stdout as a LineWriter, so println!() flushes on every newline.
'script -qfec' is preinstalled (bsdmainutils) on Ubuntu runners.

Centralized into scripts/wait-for-bg-binary.sh so the same probe
applies at all 4 spawn sites (3 node-builder + 1 remote-prover).

* Revert "fix(ci): wait for node-builder readiness via pty (real fix)"

This reverts commit 0ad7700.
…ENT_REF v0.14.4 → v0.14.5 (0xMiden#59)

* fix(ci): introduce wait-for-grpc.sh with strict 3-digit HTTP code check (sibling of next)

Sibling fix to next-side. PR 0xMiden#57 (the original main-side flake fix)
shipped a probe that false-positived on shard-4: reported 'gRPC
dispatch responsive after 1 attempt (HTTP 000000)' and tests then
ran against a not-yet-ready server, hitting the original
TypeError: Failed to fetch.

Two issues:
1. The contrived gRPC-web payload `--data-binary "$(printf '\x00\x00...')"`
   tripped bash's null-byte stripping in command substitution. The
   payload was always empty after substitution; the printf was useless.
2. The success check `[ "$http_code" != "000" ]` was too loose. On
   the failing run curl somehow emitted "000000" (a six-digit string,
   probably from an internal retry concatenation), which 'is not 000'
   and the loop broke immediately.

Fix: drop the fancy POST, use a plain GET (any HTTP response from
tonic — even 404/405/415 — proves the dispatcher is up). Tighten
the success regex to require an exact 3-digit [1-5][0-9][0-9] code.
Failures and timeouts continue to produce '000' which now correctly
fails the regex and loops.

Note: PR 0xMiden#57 was never merged. This PR brings the script forward
plus the test.yml wiring (the 3 spawn site replacements that were
in PR 0xMiden#57).

* ci(test): wire wait-for-grpc.sh into all 3 spawn sites

* ci: bump MIDEN_CLIENT_REF v0.14.4 -> v0.14.5

The HTTP-probe readiness check (introduced earlier in this PR) works
on next branch where MIDEN_CLIENT_REF=dab6cf7b... but breaks main on
v0.14.4 — every test fails with TypeError: Failed to fetch even
though the probe gets HTTP 200. The older testing-node-builder
appears to misbehave after handling a non-gRPC HTTP request.

Bump to v0.14.5 to pick up the corresponding stable client release;
the probe should be compatible with this slightly newer binary.
Required by npm's provenance verifier. Without it, publishes via
trusted publishing fail with:

    422 Unprocessable Entity
    Failed to validate repository information: package.json:
    "repository.url" is "", expected to match "https://github.com/0xMiden/web-sdk"

react-sdk and vite-plugin already had the field; only web-client
was missing. Discovered when 0.15.0-alpha.3 published the platform
packages but rejected the web-client publish on next.
…Miden#62)

Observed flake: probe returns HTTP 200 once on the first attempt that
clears the connection-refused phase, exits, tests start, ALL tests fail
with 'TypeError: Failed to fetch' to the gRPC backend. The single-probe
gate isn't strict enough — a one-shot 200 (e.g. tonic-health responding
before the rest of the dispatcher is fully wired) currently passes.

Upgrade the readiness signal to N consecutive HTTP successes spaced
PROBE_INTERVAL apart (defaults: 3 successes, 0.5s apart), so the probe
only declares the server ready after ~1s of demonstrably-stable
response. Any non-success in the streak resets it to zero and the
slow-poll loop resumes — so a momentary blip during init doesn't get
counted twice on either side.

Tracked occurrences across recent PR runs: web-sdk PR 0xMiden#23 ci-shard-4,
PR 0xMiden#29 ci-shard-1 + ci-shard-4, PR 0xMiden#27 multiple shards.
* ci: auto-patch miden-client dep from PR description marker

When a web-sdk PR description contains:

    Client PR: #1234
    Client PR: 0xMiden/rust-sdk#1234   (cross-repo / fork form)

a new composite action (.github/actions/inject-linked-client-pr) parses
the marker, resolves the linked PR's head ref, and rewrites web-sdk's
miden-client (and miden-client-sqlite-store) dep in place to point at
that branch — only on the runner, never committed. The build then runs
against the unreleased upstream code, while the committed Cargo.toml
diff stays clean. Replaces the manual '[patch]' / branch-edit dance
that this session has been carrying on every migration PR.

Why in-place rewrite instead of [patch.crates-io] / [patch."<url>"]?
Web-sdk's 'next' pins miden-client at git+url=...miden-client.git+
branch=next; pointing the same URL at a different branch via [patch]
errors with 'patches must point to different sources'. In-place
rewrite covers both 'main' (crates.io dep) and 'next' (git dep)
uniformly — the rewritten line is wrapped in marker comments that
preserve the original verbatim so a cleanup step can restore it.

Components:

  .github/actions/inject-linked-client-pr/action.yml
    Composite action: parse marker, resolve head, validate state
    (closed-without-merge fails the run loudly), patch Cargo.toml +
    refresh Cargo.lock, post a sticky PR comment summarizing the
    patch. Strict 0-or-1 comment invariant: only fires when called
    with comment=true (one designated job per workflow run), and
    deletes the prior comment if the marker is later removed.

  .github/workflows/build.yml + test.yml
    Wired the action into the cargo-compiling jobs (build-wasm,
    build-web-client-dist-folder, verify-release-build). build-wasm
    is the single comment-poster (comment=true).

  .github/workflows/check-linked-client-pr.yml
    Mergeability gate: keeps a 'linked-client-pr-ready' check on the
    PR. Stays pending while the linked client PR isn't merged-and-
    reachable from the target branch's canonical refs (miden-client
    next for next-targeted PRs, latest miden-client release tag for
    main-targeted). Re-evaluates every 15 min so the check goes green
    automatically once upstream catches up — no need to re-push.
    Configure branch protection to require it.

  scripts/dev-with-client-pr.sh
    Local-dev mirror: applies the same in-place dep rewrite to your
    working tree. Idempotent. '--clear' restores the originals
    byte-for-byte from the marker block.

  lefthook.yml
    Pre-commit guard: refuses any commit while the marker block is
    present in Cargo.toml. So a forgotten 'apply' can't accidentally
    leak into a commit.

  CLAUDE.md
    Documents the marker convention, the local script, and the
    mergeability gate.

* fix(ci): use POSIX [[:space:]]* instead of \s in marker grep

GNU grep on the runner balked with 'repetition-operator operand invalid'
on the \s* in the regex. \s is a Perl-extension class that grep -E
doesn't recognize in POSIX-extended mode; the * after it ends up
applying to nothing, hence the error. Replace with [[:space:]]* across
the composite action, the dev script, and the readiness gate workflow.

* fix(ci): anchor Client PR marker regex to start-of-line

Without the ^[[:space:]]* prefix, doc examples or table cells in the
PR body that mention 'Client PR:' inline (e.g. inside backticks for a
test-plan checklist) are picked up as if they were real markers — the
action then tries to fetch a fake miden-client PR and fails with
'Failed to fetch'. Anchoring requires the marker to start its own
line, so inline mentions in surrounding prose stay decorative. Doc
examples that use placeholders like #<NUM> instead of #N (a literal
number) are also rejected by the existing #[0-9]+ requirement.

Pairs with the PR-body cleanup that replaces the literal example
'Client PR: #1234' with 'Client PR: #<NUM>' so it's clearly a template.
gh's REST API returns pull-request state as 'open' / 'closed', GraphQL
returns 'OPEN' / 'CLOSED'. The closed-PR guardrail in the composite
action and the dev script were comparing against the uppercase form,
so the closed-without-merge branch always fired even on open PRs —
the action exited with 'is closed without merge' on every run with a
'Client PR:' marker.

Normalize the API-returned value via tr to lowercase, then compare to
'open'. Verified against miden-client#2059 (state='open' from REST).
…ment (0xMiden#69)

The auto-patch action's parse + Cargo.toml-rewrite steps succeed, but
the sticky-comment step fails with 'Resource not accessible by
integration' because build.yml only declares contents: read at the
workflow level. The error short-circuits the composite action and the
build job fails before cargo even runs.

Two-part fix:

  build.yml -> grant pull-requests: write to the build-wasm job only.
    Scoped to that job (rather than at the workflow level) so every
    other job keeps the read-only default. Principle of least privilege:
    only the comment-poster needs the write.

  inject-linked-client-pr/action.yml -> mark the sticky-comment steps
    (both upsert and delete) as continue-on-error: true. The comment is
    decorative — if write permissions ever go missing again, or if the
    sticky-comment marketplace action has a transient blip, we shouldn't
    abort the rewrite step's gains. The patch is already on disk at the
    point those steps run.
…iden#71)

PR 0xMiden#25's verification run surfaced a coverage gap: my initial wiring
in 0xMiden#65 only patched build.yml's build-wasm and test.yml's
build-web-client-dist-folder + verify-release-build. But Clippy WASM
(in lint.yml) and publint + attw (in check-publish.yml) ALSO compile
cargo against the workspace's miden-client dep — without the patch,
they hit canonical crates.io `miden-client = "0.14.5"` and fail
with `error[E0599]: no variant named `ApplyTransactionAfterSubmitFailed``
when the PR depends on an unreleased upstream variant.

Add the action call after checkout in both jobs. Both run with
comment=false (the default); build-wasm remains the single sticky-
comment poster.
…0xMiden#73)

Two issues from PR 0xMiden#25's verification run:

  1. The 'linked-client-pr-ready' custom commit status the gate posts
     never landed. Cause: the workflow's permissions block has
     'checks: write' (for check-runs) but not 'statuses: write' (which
     the POST /repos/.../statuses/$sha endpoint requires). The gh api
     POST 403'd silently — and #2 below ate the error.

  2. set_status() redirected gh's stdout to /dev/null. With no error
     visible, the silent 403 looked like 'job ran successfully' even
     though the readiness verdict never reached the commit.

This commit:
  - adds 'statuses: write' to the workflow permissions
  - drops the >/dev/null redirect so any future API failure is loud
0xMiden#75)

Previous form:
  read -r body base_ref state <<<"$(gh api ... --jq '"\(.body // "")\t\(.base.ref)\t\(.state)"')"

PR bodies are multi-line and the marker is itself space-separated. read
defaults to whitespace IFS and only consumes through the first newline,
so a body starting with 'Client PR: #2059\n...' assigned:
  body  = 'Client'
  base_ref = 'PR:'
  state = '#2059'

The gate then hit the early-return:
  if [ "$state" != "open" ] && [ "$state" != "OPEN" ]; then
    echo "PR #$PR_NUM is $state — skipping."
    exit 0
  fi

…and exited with the visible-in-the-job 'PR 0xMiden#25 is #2059 — skipping.',
never posting the linked-client-pr-ready custom status. The job logged
'success' because exit 0 is what the early-return does, but the
readiness verdict never reached the commit.

Fix:
  - body = separate gh api call (multi-line tolerated naturally)
  - base_ref + state packed into a tab-separated single-line, read with
    IFS=$'\t' so a hypothetical future whitespace in either field
    wouldn't repeat the bug
  - Same IFS=$'\t' added to the second read of (merged, merge_commit_sha)
    for consistency, even though neither field can contain whitespace
    today
…arker (0xMiden#77)

The README's Contributing section was a stub. Move it into a proper
CONTRIBUTING.md (GitHub auto-detects this filename and surfaces it on
new-issue / new-PR pages) and expand it with the cross-repo workflow:

  - The 'Client PR: #N' marker convention (and the cross-repo /
    fork form 'Client PR: 0xMiden/miden-client#N')
  - What CI does with the marker (auto-patch action, sticky PR
    comment, readiness gate)
  - Branch-protection guidance (require linked-client-pr-ready, NOT
    the gate matrix job)
  - Local-dev parity (scripts/dev-with-client-pr.sh)
  - The three situations where you still want to hand-edit
    Cargo.toml instead of using the marker

README's Contributing section is now a one-line pointer.
…DK API changes (0xMiden#127)

* docs(CLAUDE.md): document required surfaces for MidenClient + React-SDK API changes

Adds a 'Documenting public-API changes' section to the repo CLAUDE.md
that maps every doc surface a contributor has to touch when changing the
public API of either the MidenClient resource layer (web-client) or the
React SDK. Replaces the vague 'update relevant per-package CLAUDE.md'
bullet in the Contributing checklist with a precise mapping table.

Surfaces enumerated for each side: TS types (api-types.d.ts), resource
JSDoc, README narrative, per-package CLAUDE.md, root CHANGELOG, and the
typedoc curation file when applicable. Includes triggers (when to touch
each), conventions (terse tone, no speculative docs, cross-link PRs,
one source of truth per fact), and a doc-only-PR escape hatch.

Triggered by web-sdk#31, where the new transactions.batch API needed
docs in five different places — easy to miss without an explicit map.

* docs(CLAUDE.md): add the published-docs-site surfaces to the doc-process map

The first pass missed three load-bearing surfaces of the public-docs
pipeline:

1. The canonical user-facing site at https://docs.miden.xyz, hosted from
   0xMiden/miden-docs, ingesting upstream repos via deploy-docs.yml's
   docs/external/src/* → docs/builder/<repo>/ copy step.

2. The typedoc-generated API reference at docs/typedoc/web-client/, which
   is regenerated from docs-entry.d.ts and verified by CI via
   git diff --exit-code (drift fails the build).

3. docs/external/src/ as the canonical narrative source the deploy-docs
   workflow ingests. After the web/WASM split this directory needs to
   exist in this repo (currently a gap — miden-client's docs/external/src
   no longer carries web/React content), and pages under it need to be
   added when shipping new public capabilities.

Adds a 'Where the docs are published' table at the top of the doc-process
section, plus a typedoc-regen workflow snippet, plus README ⇄ Docusaurus
parity rule. Expands both the MidenClient and React SDK surface tables
to call out docs/external/src/ and docs/typedoc/web-client/ explicitly,
and updates the contributing-checklist item 3 to enumerate them.

* fix(clippy): drop redundant & in export.rs format! arg

Newer nightly clippy fires `useless_borrows_in_formatting` on the
pre-existing &account_id.to_string() pattern. The lint started firing
between origin/main's last green CI run (2026-04-30) and today —
nightly clippy rolls forward unpinned in this repo's Makefile.

Same fix as web-sdk#31's commit 7f9cf07. Including it here so the
CLAUDE.md doc-process change isn't blocked by an unrelated baseline
lint regression on the target branch.

* fix(fmt): collapse format! to single line per nightly fmt

After dropping the redundant & in commit 2b27e16, the call fits on one
line and cargo +nightly fmt --check fails until we collapse the
multi-line layout. Same pattern as the next-targeted PR (which already
had it on one line).
…en#129)

The first pass of the doc-process section (0xMiden#126/0xMiden#127) prescribed
'regenerate typedoc and commit the diff' for any change to the curated
public surface. That guidance was wrong: the in-repo
'Check that web client documentation is up-to-date' CI step runs
typedoc and then a git diff --exit-code over the regenerated tree, but
the directory hasn't been tracked since the web/WASM split — so the
diff is empty and the step is a warning-only smoke test, not a gate.
typedoc output is build artifact, not source.

Updates four spots in the doc-process section:
- 'Typedoc — keep in sync with the public API surface' →
  'Typedoc — regenerated by CI, don't commit'
- The MidenClient surface table row for docs/typedoc/web-client/ now
  reads 'Don't commit. Regenerated by CI...'
- The Conventions bullet 'Regenerate typedoc' became 'Don't commit
  typedoc'
- Contributing checklist item 3 mirrors that

Triggered by web-sdk#31 audit feedback: noticed the autogen tree
shouldn't have been committed there either. Companion .gitignore
addition lands separately on web-sdk#31.
WiktorStarczewski and others added 20 commits June 11, 2026 17:20
…ght project

The test asserts on idxdb-store's Js* classes and the wasm-bindgen
WebClient copied onto window by the browser global setup — none of which
exist under the napi binding. main had no nodejs project, so the merge
made it run there for the first time.
The test predates the main/next split and guards both directions of the
entry contract: dist/st/eager.js must have WASM initialized at import
(sync constructors work immediately), and dist/st/index.js must NOT
TLA-init WASM (sync constructor throws before getWasmOrThrow resolves).
next's js_export/napi test-suite rewrite (0xMiden#13) deleted it, leaving the
package's default entry (main/browser → dist/st/eager.js) untested;
the merge inherited that deletion and the knip ignoreUnresolved entry
for ./eager.js went stale — restoring the test un-stales it.
Excluded from the nodejs Playwright project (page fixture +
browser-only dynamic imports). Verified green locally against the
merged dist.
- ci: eager_entry.test.ts added to ci-shard-4's testMatch — main carried
  it in shard-4, the merge dropped the entry, and CI only invokes the
  shard projects, so the restored test was never executing in CI
- react-sdk: the variant rewrite in tsup.config.ts keyed off the
  /lazy specifier, but next's source tree imports the bare
  @miden-sdk/miden-sdk — the rewrite never matched and every variant
  (/lazy, /mt, /mt/lazy) silently bundled the eager-ST SDK. The rewrite
  now keys off the exact quoted bare specifier and THROWS when nothing
  matches, so a future specifier change cannot silently ship wrong
  variants again. Verified: all four dist bundles emit their correct
  per-variant subpath
- node: waitForIdle resolves immediately (the napi binding has no
  detached call chain to drain) and lastAuthError returns null (signing
  goes through FilesystemKeyStore — a JS sign callback can never run),
  so the cross-platform MidenClient type surface holds on Node instead
  of throwing TypeError
- docs: lastAuthError docstrings state the useWorker:false requirement
  (the worker shim's keystore lives in the worker WASM instance, so the
  main-thread accessor reads null there)
- test: with_inner_web_client_reentrancy first case comment matched
  main's serializing proxy; next's proxy binds fallback reads directly,
  so the case guards the borrow-check invariant, not chain re-entrancy
…roject

Same class as no_wasm_reentry_via_tojson: the tests came from main
(which had no nodejs Playwright project) and exercise the browser
worker-shim chain mechanics — _serializeWasmCall, the
_withInnerLockDepth counter, chain release on rejection. The napi
client serializes in Rust and has no JS call chain, so all four cases
TypeError'd under nodejs. They were masked in the previous run because
no_wasm_reentry aborted the suite earlier.
Bumps @miden-sdk/miden-sdk, @miden-sdk/react (+ peer), and the three
node-sdk platform packages to 0.15.0-alpha.5. First alpha published
from the unified branch (PR 0xMiden#177): restores useWorker, newCallbackProver,
_withInnerWebClient, MidenClient.ready/waitForIdle/lastAuthError,
BlockHeader.feeFaucetId, the miden-mobile-prover crate, and MT proving
via the /mt + /mt/lazy subpaths on the 0.15 protocol surface.

Release notes must flag: nightly toolchain required for the MT build;
react-sdk is now esm-only; mobile-prover wire format requires a matching
0.15 SDK (0.14-built native binaries do not interoperate).
…resolves the 0xMiden#180 'prove hang' (0xMiden#181)

* fix(web): initialize the rayon pool in the mock worker (INIT_MOCK)

Every prove executes inside the method worker's own WASM instance, and
rayon's global pool is per-instance — but the INIT_MOCK action never
called initThreadPool there, unlike the real INIT path beside it. Mock
proving (incl. the integration suite) therefore ran single-threaded on
the /mt build regardless of page COI or main-thread pool init: a mock
mint prove measured 23.3s vs 26.4s on the ST build (and 47.8s on
0.14.11 — the gap predates 0.15). numThreads now rides INIT_MOCK
exactly like INIT; the same prove completes in 5.2s on 10 threads.

Also ships two testing-gated MT diagnostics (mtProbeSync/mtProbeAsync)
that report which pool threads execute a par_iter from sync vs async
dispatch — these were the instruments that separated healthy pool
mechanics from the missing per-instance init while investigating the
'prove hang' in 0xMiden#180 (which this resolves: the perceived hang was the
sequential prove blowing consumer timeouts, plus an experiment that
rayon::spawn'ed into the uninitialized mock-worker registry, where jobs
wait forever for workers that don't exist).

* docs: changelog entry for the INIT_MOCK thread-pool fix
…render (0xMiden#179)

* fix(react): resolve the default prover after WASM init, not at first render

MidenProvider resolved its prover config in a render-time useMemo —
resolveTransactionProver constructs WASM objects (TransactionProver),
which crashes with `__wbindgen_malloc` undefined on the /lazy entries,
where no top-level await precedes first render. The whole consumer tree
went down with it (Miden Wallet's extension popup error boundary:
'Something went wrong while booting a wallet').

The bug was latent on every published 0.15 alpha before .5: the react
/lazy build accidentally bundled the EAGER SDK (the inert variant
rewrite fixed on the unification branch), whose import-time TLA
initialized WASM before any render. Correct lazy resolution exposed it.

The prover is now resolved as state gated on `isReady` — by then the
WebClient exists, so the WASM module is necessarily initialized. Hook
prove paths were already safe (they resolve at prove time).

* docs: changelog entry for the lazy-entry prover resolution fix

* fix(react): drop eslint-disable for unconfigured react-hooks/exhaustive-deps rule

The react-sdk ESLint config registers only @typescript-eslint, so the
react-hooks/exhaustive-deps disable directive references an undefined
rule, which ESLint reports as an error (Run lint exits 2). The rule
isn't active, so the deps array needs no suppression — remove the
directive.
)

Two fixes for the wallet leg of 0xMiden#180:

- proveTransaction(result, prover) with an explicit prover proves directly,
  without requiring an initialized client. Proving with a supplied prover is
  pure computation over the TransactionResult; the inner-client gate was
  incidental and broke prover-only hosts (e.g. a chrome.offscreen document
  proving on its own rayon pool). The explicit-prover path also no longer
  holds the inner-client lock for the duration of the prove.

- A failed method-worker INIT now rejects the wrapper's `ready` promise.
  INIT errors carry no requestId, matched no pending request, and were
  dropped — leaving `ready` forever pending and every worker-forwarded
  method call silently hanging on `await this.ready`. Callers now get the
  underlying error (e.g. the eager genesis fetch failing against an
  unreachable or version-mismatched RPC endpoint).
…t merge (0xMiden#184)

The 0xMiden#177 unification dropped the _serializeWasmCall chain from the
direct (no-worker) path everywhere it existed on main:

- createClientProxy bound fallback methods (getAccount, getAccounts,
  getTransactions, ...) raw to the WASM client instead of routing them
  through the chain (SYNC_METHODS stay raw-bound by design);
- the five transaction methods and three sync methods called the raw
  client directly in their !this.worker branches.

Every such call holds the WASM client's internal RefCell across its
awaits, so two overlapping calls panic with 'RefCell already borrowed'
and poison the instance — later calls throw 'Unreachable code should
not be executed' and in-flight promises can stay pending forever.
useWorker:false consumers (Capacitor/WKWebView, callback-prover hosts)
hit this deterministically; miden-wallet's mobile build panicked at
wallet creation when front-end balance polling overlapped the initial
syncs (the mobile leg of 0xMiden#180).

All direct-path calls now route through the chain exactly as on main
and 0.14.x. The sync methods keep their coalescing lock as the outer
lock (sync lock → chain, in that order, on every path — no deadlock).
Covered by a burst-concurrency regression test that hangs/panics on
the unfixed build and completes in ~3s on the fixed one.
* chore: update miden dependencies

* fix(tests): repair test/impl mismatches left by the next merge

- restore the NoteAttachment instance argument to createP2IDNote call
  sites in notes, note_transport and import_export integration tests.
  The WASM signature requires the instance and the merge had replaced
  it with undefined
- useMultiSend.test: the hook passes an empty attachment, so expect an
  instance rather than undefined
- utils.test: the network storage mode no longer exists in the WASM
  surface (removed in miden-client #2214). Assert it is rejected
  instead of mocking AccountStorageMode.network, and drop the unused
  network mock from accounts.test
- idxdb-store: add the detailsCommitment field (now the OutputNotes
  primary key) to output-note fixtures in sync.test and to all
  upsertOutputNote calls in notes.test

* docs: fill in PR links for the 0.15.0 changelog entries carried by 0xMiden#171

* chore: bump to client crates.io dep

* fix(ci): migrate test infra from node-builder to miden-client's start-test-node.sh

miden-client v0.15.0 removed the testing node-builder and testing-remote-prover
crates. Test infra is now provisioned the same way miden-client's own CI does
it: scripts/start-test-node.sh cargo-installs the standalone node executables
(miden-validator, miden-node, miden-ntx-builder, miden-remote-prover) at the
node rev pinned in its Cargo.lock and bootstraps a fresh genesis from the
test-node-genesis crate.

- replace build-node-builder with build-test-node (install + gen-genesis
  prebuild, cached on node rev / client ref, uploaded as test-node-artifact)
- drop build-remote-prover: the tx prover ships with the test node on 50051
- consumer jobs check out miden-client, download the binaries, and run
  start-test-node.sh --background (waits for RPC on 57291 itself)
- drop scripts/wait-for-grpc.sh (no remaining callers)

---------

Co-authored-by: igamigo <ignacio.amigo@lambdaclass.com>
chore(release): merge `next` into `main` for v0.15.0
Persist a lineage per partially-fillable swap order — the chain of remainder
notes a PSWAP leaves behind as it is filled round by round — keyed by a stable
orderId. Adds a `pswap` resource on MidenClient (lineages / lineagesFor /
lineage / cancelByOrder) and four React hooks (usePswapLineages,
usePswapLineagesFor, usePswapLineage, usePswapCancelByOrder).

- pswap.rs: lineage reads + build_pswap_cancel_by_order with a terminal-state
  guard (only Active lineages can be cancelled), covering the raw binding, the
  pswap.js resource, and the React hook through the one shared layer.
- PswapLineageRecord model exposes remainingOffered/remainingRequested as
  FungibleAsset (faucet + amount), depth, tip, state, and block numbers.
- applyTransaction routes through the high-level apply so registered tx
  observers (PSWAP tracking) fire.
- Tracks inicio-labs/miden-client vaibhav/pswap until the PSWAP API releases.
VAIBHAVJINDAL3012 and others added 7 commits June 17, 2026 18:36
…itative

The cancelByOrder resource duplicated the FullyFilled/Reclaimed check that
buildPswapCancelByOrder already enforces in Rust, and its comment described
the pre-guard behavior (request reaching the kernel). Remove the JS state
block so the binding is the single guard, matching usePswapCancelByOrder;
keep the lineage fetch for the creator account.
* chore(deps): upgrade miden-client to 0.15.2

Bumps miden-client + miden-client-sqlite-store 0.15.0 -> 0.15.2 (pulls
miden-note-transport-proto-build 0.4.1; miden-protocol resolves to
0.15.3). Updates the CI node-builder ref (MIDEN_CLIENT_REF) to the
v0.15.2 tag commit so the node's miden-protocol matches the bundled
client. 0.15.2 deprecates the no-hint Client::send_private_note; the
web-client keeps the no-hint path (allow(deprecated)) since the JS API
does not expose a block hint yet.

* release: 0.15.1
0.15.2 ships the PSWAP API on crates.io, so pin the version and drop the
inicio-labs/vaibhav/pswap git dependency. Adapt to 0.15.2's PswapLineageRecord:
remainingOffered/remainingRequested are now plain amounts (the record no longer
carries the faucet — it's recovered from the original note when needed), and the
created/updated block accessors are gone (fields dropped upstream). Suppress the
new send_private_note deprecation; the block-hint variant can be wired later.
The "Publish Rust Crates on Release" job has failed since v0.15.0:
- web-client depends on js-export-macro via a bare path dep (no version),
  so cargo publish refuses it, and js-export-macro was never in the
  publish list.
- The Rust crates share the workspace version, which releases don't bump
  (only the npm versions move), so cargo publish hits "already exists".

Fixes:
- Bump the workspace version + internal dep version refs to 0.15.1.
- Add js-export-macro as a workspace dep (path + version); web-client uses
  it via workspace = true so the published manifest carries a version.
- Publish in dependency order (js-export-macro, idxdb-store, web-client,
  mobile-prover) and add the missing js-export-macro step.
- web-client publishes with --no-verify: it's a wasm cdylib that can't
  build for the host target; CI verifies it for wasm32 instead.
- Document the dual npm/Rust version bump in CONTRIBUTING.

Verified: js-export-macro/idxdb-store/mobile-prover dry-run-publish
cleanly at 0.15.1; web-client's manifest now resolves (its deps publish
first in the workflow).
Add a workflow_dispatch trigger (with an optional ref input) to
publish-crates-release.yml so the crates can be (re-)published off a
chosen ref — e.g. to catch crates.io up to a fix that landed after a
release tag, or to recover when a release's publish failed partway.
The checkout ref falls back from the release tag to the dispatched ref.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants