Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8ce7b25
docs: import 5 web-SDK-relevant planning docs from miden-client (#14)
WiktorStarczewski Apr 28, 2026
4b1e98d
chore(react-sdk): align eslint + typescript-eslint deps with root ove…
WiktorStarczewski Apr 28, 2026
b66b127
chore: add .nvmrc + lefthook + lint-staged for local dev hygiene (#19)
WiktorStarczewski Apr 28, 2026
2413dd7
chore(web-client): drop mocha/chai/puppeteer/esm/ts-node devDeps (#18)
WiktorStarczewski Apr 28, 2026
bd36080
chore(deps): bump miden-client on `main` to 0.14.5 (#34)
WiktorStarczewski Apr 28, 2026
76324c4
chore(ci): add publint + attw gates and fix exports maps (#15)
WiktorStarczewski Apr 28, 2026
57b35a6
chore: add vitest workspace + root test script (#21)
WiktorStarczewski Apr 28, 2026
e4c2303
chore: add knip in strict mode (53 baseline findings cleared) (#17)
WiktorStarczewski Apr 28, 2026
3c62171
chore: drop stylistic eslint rules; let prettier own formatting (#20)
WiktorStarczewski Apr 28, 2026
a2096e0
ci: publish react-sdk and vite-plugin alongside web-client on release…
WiktorStarczewski Apr 28, 2026
c8502c6
ci(publish): rename crates secret to CARGO_REGISTRY_TOKEN (#37)
WiktorStarczewski Apr 28, 2026
d34e81b
docs: add top-level CLAUDE.md, refresh react-sdk usage guide, drop st…
WiktorStarczewski Apr 28, 2026
72b2a9a
Merge main into next
WiktorStarczewski Apr 28, 2026
bcf093b
Wire eager/lazy split into next's web-client (preserve napi node entry)
WiktorStarczewski Apr 28, 2026
71fe185
fix(ci): drop unused deps + tighten exports for publint/attw + knip
WiktorStarczewski Apr 28, 2026
b368bef
fix(ci): re-add @vitest/coverage-v8 to idxdb-store, ignore in knip
WiktorStarczewski Apr 28, 2026
9f322bc
fix(idxdb-store): restore vitest coverage config (lets knip auto-disc…
WiktorStarczewski Apr 28, 2026
ec82d85
test(idxdb-store): port main's full test suite + restore 95% coverage…
WiktorStarczewski Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ name: Build
on:
push:
branches: [main, next]
paths-ignore:
- "**.md"
- "**.txt"
- "docs/**"
pull_request:
paths-ignore:
- "**.md"
- "**.txt"
- "docs/**"

permissions:
contents: read
Expand All @@ -23,8 +15,37 @@ env:
CARGO_PROFILE_DEV_DEBUG: 0

jobs:
# Pre-flight: detect whether any non-docs files changed. We deliberately
# trigger this workflow on EVERY push/PR (no top-level paths-ignore) so
# required status checks always report a state — `paths-ignore` at the
# workflow level prevents the workflow from running at all on docs-only
# PRs, which leaves required checks stuck in "Expected" forever and blocks
# merging. Instead, every downstream job gates on this filter's output and
# reports `skipped` when there's nothing relevant to build (skipped jobs
# satisfy required checks).
changes:
name: Detect relevant changes
runs-on: ubuntu-24.04
outputs:
non_docs: ${{ steps.filter.outputs.non_docs }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
id: filter
with:
# `non_docs` matches whenever a changed file is NOT a doc/text file
# under one of the listed paths. Mirror of the previous
# `paths-ignore` set.
filters: |
non_docs:
- '!**/*.md'
- '!**/*.txt'
- '!docs/**'

build-wasm:
name: Build Client for Wasm
needs: [changes]
if: needs.changes.outputs.non_docs == 'true'
runs-on: ubuntu-24.04
env:
# See test.yml's build-web-client-dist-folder for rationale.
Expand Down
109 changes: 109 additions & 0 deletions .github/workflows/check-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Check Publish
permissions:
contents: read
on:
push:
branches: [main, next]
paths-ignore:
- "**.md"
- "**.txt"
- "docs/**"
pull_request:
paths-ignore:
- "**.md"
- "**.txt"
- "docs/**"

concurrency:
group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true

env:
CARGO_PROFILE_DEV_DEBUG: 0
RUST_CACHE_KEY: rust-cache-2026.02.18

jobs:
check-publish:
# Runs publint + arethetypeswrong (attw) against the three published
# packages: @miden-sdk/miden-sdk (WASM web-client), @miden-sdk/react,
# @miden-sdk/vite-plugin. Each tool runs against the actual `pnpm pack`
# tarball, so the dist/ output must exist first — hence we build all
# three packages before invoking the gates.
#
# WASM build setup mirrors test.yml's build-web-client-dist-folder job
# (sccache + rust-cache + binaryen + MIDEN_FAST_BUILD on PRs) so PR CI
# stays in the same ballpark.
name: publint + attw
runs-on: ubuntu-24.04
env:
SCCACHE_GHA_ENABLED: "true"
steps:
- uses: actions/checkout@v6

- name: Install Rust (needed for WASM build)
run: |
rustup update --no-self-update
rustup target add wasm32-unknown-unknown

# See test.yml for why these are forwarded explicitly.
- name: Configure sccache cache backend (v2)
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');

- name: Run sccache
uses: mozilla-actions/sccache-action@v0.0.10
continue-on-error: true
id: sccache-install

- name: Probe sccache and enable wrapper if healthy
if: steps.sccache-install.outcome == 'success' && env.SCCACHE_PATH != ''
shell: bash
run: |
probe_out=$("$SCCACHE_PATH" --start-server 2>&1) && rc=0 || rc=$?
echo "$probe_out"
if [ $rc -eq 0 ]; then
echo "RUSTC_WRAPPER=$SCCACHE_PATH" >> "$GITHUB_ENV"
echo "sccache enabled as RUSTC_WRAPPER ($SCCACHE_PATH)"
else
echo "sccache --start-server failed (rc=$rc); running without wrapper"
fi

- name: Install binaryen (wasm-opt)
run: |
sudo apt-get update && sudo apt-get install -y binaryen

- name: Add Rust Cache
uses: Swatinem/rust-cache@v2
with:
shared-key: rust-wasm
prefix-key: ${{ env.RUST_CACHE_KEY }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}

- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

# MIDEN_FAST_BUILD skips wasm-opt and uses the release-fast cargo
# profile. The publint/attw gates inspect the package shape and type
# surface, not the optimization level of the WASM blob, so the fast
# profile is fine here. The canonical full-optimization build is
# exercised by test.yml's verify-release-build job on push.
- name: Run check:publish (build + publint + attw)
run: MIDEN_FAST_BUILD=true pnpm run check:publish

- name: Show sccache stats
if: always()
run: sccache --show-stats || true
19 changes: 19 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ jobs:
run: pnpm install --no-frozen-lockfile
- run: make rust-client-ts-lint

knip:
name: Knip (unused code/deps)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Knip
run: pnpm run check:knip

clippy-wasm:
name: Clippy WASM
runs-on: ubuntu-24.04
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
11 changes: 11 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,16 @@
*.yml
*.yaml

# Build / tooling output
**/dist/**
**/target/**
**/node_modules/**

# Generated TypeScript declarations
**/*.d.ts

# Generated JS (codegen)
crates/idxdb-store/src/js/**

# Vendored docs
docs/**
127 changes: 127 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# CLAUDE.md — repo notes for AI agents

Conventions and tooling notes for `0xMiden/web-sdk`. End-user docs live in [README.md](README.md); per-package usage guides live alongside the packages (e.g. [`packages/react-sdk/CLAUDE.md`](packages/react-sdk/CLAUDE.md)).

## What this repo is

A pnpm monorepo holding the JS / WASM / React bits previously part of [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client). Five published artifacts:

| Artifact | Path | Registry |
|---|---|---|
| `@miden-sdk/miden-sdk` | `crates/web-client/` (Rust + WASM + JS bindings) | npm |
| `@miden-sdk/react` | `packages/react-sdk/` | npm |
| `@miden-sdk/vite-plugin` | `packages/vite-plugin/` | npm |
| `@miden-sdk/node-{darwin-arm64,darwin-x64,linux-x64-gnu}` | `packages/node-sdk-*` | npm (platform-specific native binaries; consumed via `optionalDependencies` on `@miden-sdk/miden-sdk`) |
| `miden-idxdb-store` | `crates/idxdb-store/` | crates.io |

The `Cargo.toml` workspace dep `miden-client = "x.y.z"` pins compatibility with the upstream Rust crate. Changes to shared types (Account, Note, gRPC schema, …) usually need a coordinated PR in `0xMiden/miden-client` first.

## Toolchain

- **Package manager**: pnpm 9 (workspace at `pnpm-workspace.yaml`). **Never** use `yarn` or `npm install` — they will desync the lockfile.
- **Node**: ≥ 20 (`engines.node` in `package.json`, `.nvmrc`).
- **Rust**: stable 1.93 + nightly (for `cargo +nightly fmt`, `clippy`, and `fix`). Pinned in `rust-toolchain.toml`.
- **Lefthook** runs pre-commit; `pnpm install` wires it via the `prepare` script.

## Build / lint / test

Drive everything through the `Makefile` — never call `cargo fmt` directly (the project requires nightly + an exact prettier/eslint pass that vanilla `cargo fmt` skips).

```bash
make help # list targets

# Build
make build-wasm # WASM crates only (wasm32-unknown-unknown)
make build-web-client # WASM + JS bindings + dist
make build-react-sdk # everything @miden-sdk/react needs

# Lint + format
make format # nightly cargo fmt + prettier write + eslint --fix
make format-check # CI form (no writes)
make clippy-wasm # clippy for both WASM crates
make typos-check # spellcheck
make lint # umbrella: fix-wasm + format + clippy-wasm + typos + checks
make web-client-check-methods # verifies every WASM method is classified in the JS proxy

# Test
make test-coverage # all coverage gates (react-sdk + idxdb-store + vite-plugin + web-client unit)
make test-react-sdk # vitest unit (jsdom)
make test-web-client-unit # vitest unit (web-client)
make integration-test-web-client # playwright (chromium); accepts SHARD_PARAMETER
make integration-test-web-client-webkit
```

CI (`.github/workflows/test.yml`) runs all of the above on every PR. `main` and `next` warm sccache + Swatinem/rust-cache.

## Coverage thresholds

`packages/react-sdk/vitest.config.ts` enforces `lines / branches / functions / statements ≥ 95`. Two files are excluded because they require the real WASM binary and are covered by Playwright integration tests:

- `src/utils/accountBech32.ts` — covered by `test/accountBech32.test.ts`
- `src/hooks/useAssetMetadata.ts` — covered by `test/useAssetMetadata.test.ts`

**Always run `make test-react-sdk` locally before pushing** — CI will block the merge if any threshold dips. Lowering thresholds is not the right fix; either add tests or move the file to the excluded list with justification.

## WASM concurrency: `runExclusive`

The wasm-bindgen `WebClient` is **not** safe under concurrent access. Calls that go through it from multiple call sites must serialize via the AsyncLock exposed by `MidenProvider`:

```ts
const { runExclusive } = useMiden();
await runExclusive(async (client) => { /* … */ });
```

Symptom of a violation: `Error: recursive use of an object detected which would lead to unsafe aliasing in rust`. The `crates/web-client/test/sync_lock.test.ts` integration test guards against regressions — if you add a hook that touches the client, route it through `runExclusive` (or one of the existing serialized helpers) or the lock test will fail.

## Eager vs lazy entry points

`@miden-sdk/miden-sdk` ships two entry points with identical APIs but different init behaviour:

| Specifier | When WASM loads | Use when |
|---|---|---|
| `@miden-sdk/miden-sdk` | At import (top-level await) | Vite/Webpack browser bundles where TLA is fine |
| `@miden-sdk/miden-sdk/lazy` | On first `await MidenClient.ready()` (or first awaited SDK method) | SSR (Next.js, Remix, SvelteKit), Capacitor WKWebView hosts, anywhere TLA is unsafe |

Same split applies to `@miden-sdk/react` (`react/lazy` pulls `miden-sdk/lazy`). The eager/lazy contract is guarded by `crates/web-client/test/eager_entry.test.ts` — if you change the public API in one entry, mirror it in the other and re-run the type-check scripts under `crates/web-client/scripts/`.

## Releases

Two long-lived branches:

- **`main`** → npm `latest` dist-tag. Released on GitHub release events.
- **`next`** → npm `next` dist-tag. Released when a PR merges into `next` carrying the `patch release` label.

Both branches have protection enabled; required status checks mirror across the two.

The release-publish gate compares the local `package.json` version against the **npm registry** (not against the previous git commit) — see `scripts/check-{web-client,react-sdk,vite-plugin}-version-release.sh`. So a release tag publishes whichever of the four packages have versions not yet on npm; bumping a single package is a clean release of just that one.

WASM size is gated at 25 MB in the publish workflow — if `wasm-opt` ever silently fails, the bloated binary never reaches npm.

Crate publishing (`miden-idxdb-store`, `miden-client-web`) goes through `.github/workflows/publish-crates-release.yml` and uses the `CARGO_REGISTRY_TOKEN` org secret.

## Gotchas worth remembering

- **No yarn.** The repo migrated from yarn to pnpm. If you see a doc, comment, or script that says `yarn ...`, it's stale — fix it (or flag it).
- **Don't chain `pnpm --filter ... -- arg` through npm-script `&&`.** pnpm's argument forwarding only wires through to the LAST command in the chain. The Makefile splits multi-step playwright invocations across explicit Make recipes for this reason; preserve that pattern (see `integration-test-web-client` in `Makefile`).
- **Test sharding is manually balanced.** `packages/react-sdk/playwright.config.ts` defines four CI shard projects (`ci-shard-1` … `ci-shard-4`) with explicit `testMatch` arrays sized empirically from observed run timings. Rebalance by moving file paths between arrays — no workflow edits needed. Comment block at the top of the config explains the history.
- **Network-bound tests don't belong in CI.** Anything that hits a live RPC node (testnet/devnet) is excluded. If you add such a test, gate it on an env var and skip by default.
- **Account ID display.** Hooks accept hex (`0x…`) and bech32 (`mtst1q…`) interchangeably. Bech32 prefix tracks the active network — `mtst1` for testnet/devnet, `mid1` for mainnet (when it lands). Don't hardcode prefixes.

## Cross-repo coordination

| Concern | Repo |
|---|---|
| Shared Rust types, gRPC schema, `MidenClient` semantics | [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client) |
| Account compiler, MASM standard library, base protocol types | [`0xMiden/miden-base`](https://github.com/0xMiden/miden-base) |
| MidenFi browser-extension wallet adapter | [`0xMiden/miden-wallet-adapter`](https://github.com/0xMiden/miden-wallet-adapter) |
| Para signer integration | [`0xMiden/miden-para`](https://github.com/0xMiden/miden-para) |
| Turnkey signer integration | [`0xMiden/miden-turnkey`](https://github.com/0xMiden/miden-turnkey) |

PRs that touch the WASM/JS boundary often need a synchronized PR in miden-client — bump the workspace dep and verify the integration tests still pass.

## Contributing checklist

1. `make lint` clean.
2. `make test-coverage` clean (and locally verify thresholds before pushing).
3. For changes to public API: update the relevant per-package CLAUDE.md (e.g. `packages/react-sdk/CLAUDE.md` for hook signatures) and the type-check scripts under `crates/web-client/scripts/`.
4. For changes to release flow: cross-check both `publish-web-client-release.yml` (latest channel) and `publish-web-client-next.yml` (next channel) — they intentionally mirror each other.
Loading
Loading