Skip to content

fix: lazy-load wasm-mps to fix browser wasn initialisation#8635

Merged
Marzooqa merged 1 commit intomasterfrom
WCI-244
Apr 28, 2026
Merged

fix: lazy-load wasm-mps to fix browser wasn initialisation#8635
Marzooqa merged 1 commit intomasterfrom
WCI-244

Conversation

@Marzooqa
Copy link
Copy Markdown
Contributor

@Marzooqa Marzooqa commented Apr 27, 2026

Problem

sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts used a static top-level import:

import { ed25519_dkg_round0_process, ... } from '@bitgo/wasm-mps';

TypeScript compiles this to a synchronous require() in CJS output. The CJS build of @bitgo/wasm-mps
calls fs.readFileSync(wasmPath) at module load time to initialise the WASM binary. fs does not exist in
the browser, so all three round functions are never initialised — causing silent undefined errors when
sdk-core attempts EdDSA MPCv2 key generation in a browser environment.

Fix

Mirrors the existing ECDSA DKG pattern (ecdsa-dkls/dkg.ts):

  • Removes the static top-level import
  • Adds a private loadWasmMps() method that lazily calls await import('@bitgo/wasm-mps') — the bundler
    resolves this to the ESM build in browser and CJS in Node. No typeof window check is needed since
    wasm-mps ships a single package with dual ESM/CJS exports (unlike dkls which has separate node/web
    packages)
  • Makes initDkg() async — WASM is loaded once on first call
  • All WASM calls go through getWasmMps() which throws if the module isn't loaded
  • Adds await to both initDkg() calls in sdk-core/eddsaMPCv2.ts

Why .mocharc.js needs experimental-wasm-modules

In tests, mocha uses require: 'tsx' which transforms TypeScript on-the-fly using esbuild in
transform-only mode. Unlike tsc, esbuild in this mode does not rewrite await import() to require() — it
leaves it as a native Node dynamic import. Node's native dynamic import uses the "import" export
condition, which resolves to the ESM build of wasm-mps. That ESM build contains import * as wasm from
"./wasm_mps_bg.wasm", which Node cannot handle without --experimental-wasm-modules.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

TICKET: WCI-244

@linear
Copy link
Copy Markdown

linear Bot commented Apr 27, 2026

Static top-level import compiled to synchronous require() in CJS, which
triggered fs.readFileSync on the .wasm binary at module load time. fs
does not exist in browser environments, so all WASM functions were
undefined when called from sdk-core.

Mirror the ECDSA DKG pattern (ecdsa-dkls/dkg.ts):
- Remove static top-level import of @bitgo/wasm-mps
- Add wasmMps field with optional constructor injection for DI
- Add private loadWasmMps() that lazily does await import('@bitgo/wasm-mps')
  (bundler resolves to ESM in browser, CJS in Node — no typeof window
  check needed as wasm-mps ships a single package with dual exports)
- Make initDkg() async; WASM is loaded once on first call
- All WASM calls go through getWasmMps() which throws if not loaded
- Add await to initDkg() calls in sdk-core/eddsaMPCv2.ts
- Update tests to await initDkg() and mark affected callbacks async

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

TICKET: WCI-244
@Marzooqa Marzooqa marked this pull request as ready for review April 27, 2026 16:14
@Marzooqa Marzooqa requested review from a team as code owners April 27, 2026 16:14
@Marzooqa Marzooqa added 5 and removed 5 labels Apr 28, 2026
@Marzooqa Marzooqa merged commit 5a58521 into master Apr 28, 2026
22 checks passed
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.

2 participants