@miden-sdk/miden-sdk 0.15.0-alpha.4 → 0.15.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +139 -9
  2. package/dist/mt/Cargo-C9UbiAcT.js +26202 -0
  3. package/dist/mt/Cargo-C9UbiAcT.js.map +1 -0
  4. package/dist/{api-types.d.ts → mt/api-types.d.ts} +160 -6
  5. package/dist/mt/assets/miden_client_web.wasm +0 -0
  6. package/dist/mt/crates/miden_client_web.d.ts +4821 -0
  7. package/dist/mt/eager.js +38 -0
  8. package/dist/mt/eager.js.map +1 -0
  9. package/dist/{index.d.ts → mt/index.d.ts} +6 -3
  10. package/dist/mt/index.js +3811 -0
  11. package/dist/mt/index.js.map +1 -0
  12. package/dist/{wasm.js → mt/wasm.js} +1 -1
  13. package/dist/mt/wasm.js.map +1 -0
  14. package/dist/mt/workerHelpers.js +28 -0
  15. package/dist/mt/workers/Cargo-C9UbiAcT-Z344cyB1.js +26203 -0
  16. package/dist/mt/workers/Cargo-C9UbiAcT-Z344cyB1.js.map +1 -0
  17. package/dist/mt/workers/assets/miden_client_web.wasm +0 -0
  18. package/dist/mt/workers/web-client-methods-worker.js +26996 -0
  19. package/dist/mt/workers/web-client-methods-worker.js.map +1 -0
  20. package/dist/{workers → mt/workers}/web-client-methods-worker.module.js +71 -2
  21. package/dist/mt/workers/web-client-methods-worker.module.js.map +1 -0
  22. package/dist/mt/workers/workerHelpers.js +28 -0
  23. package/dist/{Cargo-CVlXCH_2.js → st/Cargo-OZMlHpic.js} +721 -496
  24. package/dist/st/Cargo-OZMlHpic.js.map +1 -0
  25. package/dist/st/api-types.d.ts +1144 -0
  26. package/dist/{workers → st}/assets/miden_client_web.wasm +0 -0
  27. package/dist/{crates → st/crates}/miden_client_web.d.ts +357 -197
  28. package/dist/st/docs-entry.d.ts +38 -0
  29. package/dist/{eager.js → st/eager.js} +2 -2
  30. package/dist/st/eager.js.map +1 -0
  31. package/dist/st/index.d.ts +183 -0
  32. package/dist/{index.js → st/index.js} +723 -284
  33. package/dist/st/index.js.map +1 -0
  34. package/dist/st/wasm.js +23 -0
  35. package/dist/st/wasm.js.map +1 -0
  36. package/dist/{workers/Cargo-CVlXCH_2-CWA-5vlh.js → st/workers/Cargo-OZMlHpic-DZjvJlWc.js} +721 -496
  37. package/dist/st/workers/Cargo-OZMlHpic-DZjvJlWc.js.map +1 -0
  38. package/dist/{assets → st/workers/assets}/miden_client_web.wasm +0 -0
  39. package/dist/{workers → st/workers}/web-client-methods-worker.js +792 -498
  40. package/dist/st/workers/web-client-methods-worker.js.map +1 -0
  41. package/dist/st/workers/web-client-methods-worker.module.js +628 -0
  42. package/dist/st/workers/web-client-methods-worker.module.js.map +1 -0
  43. package/js/client.js +190 -7
  44. package/js/node/napi-compat.js +22 -3
  45. package/js/node-index.js +0 -1
  46. package/js/resources/accounts.js +4 -6
  47. package/js/resources/transactions.js +138 -1
  48. package/lazy/package.json +2 -2
  49. package/mt/lazy/package.json +4 -0
  50. package/mt/package.json +4 -0
  51. package/package.json +30 -15
  52. package/dist/Cargo-CVlXCH_2.js.map +0 -1
  53. package/dist/eager.js.map +0 -1
  54. package/dist/index.js.map +0 -1
  55. package/dist/wasm.js.map +0 -1
  56. package/dist/workers/Cargo-CVlXCH_2-CWA-5vlh.js.map +0 -1
  57. package/dist/workers/web-client-methods-worker.js.map +0 -1
  58. package/dist/workers/web-client-methods-worker.module.js.map +0 -1
  59. /package/dist/{docs-entry.d.ts → mt/docs-entry.d.ts} +0 -0
@@ -0,0 +1,3811 @@
1
+ import loadWasm from './wasm.js';
2
+ export { Account, AccountArray, AccountBuilder, AccountBuilderResult, AccountCode, AccountComponent, AccountComponentCode, AccountDelta, AccountFile, AccountHeader, AccountId, AccountIdArray, AccountInterface, AccountProof, AccountReader, AccountStatus, AccountStorage, AccountStorageDelta, AccountStorageMode, AccountStorageRequirements, AccountVaultDelta, Address, AdviceInputs, AdviceMap, AssetVault, AuthFalcon512RpoMultisigConfig, AuthSecretKey, BasicFungibleFaucetComponent, BlockHeader, CodeBuilder, CommittedNote, ConsumableNoteRecord, Endpoint, ExecutedTransaction, Felt, FeltArray, FetchedAccount, FetchedNote, FlattenedU8Vec, ForeignAccount, ForeignAccountArray, FungibleAsset, FungibleAssetDelta, FungibleAssetDeltaItem, GetProceduresResultItem, InputNote, InputNoteRecord, InputNoteState, InputNotes, IntoUnderlyingByteSource, IntoUnderlyingSink, IntoUnderlyingSource, JsAccountUpdate, JsStateSyncUpdate, JsStorageMapEntry, JsStorageSlot, JsVaultAsset, Library, MerklePath, NetworkId, NetworkNoteStatusInfo, NetworkType, Note, NoteAndArgs, NoteAndArgsArray, NoteArray, NoteAssets, NoteAttachment, NoteAttachmentScheme, NoteConsumability, NoteConsumptionStatus, NoteDetails, NoteDetailsAndTag, NoteDetailsAndTagArray, NoteExecutionHint, NoteExportFormat, NoteFile, NoteFilter, NoteFilterTypes, NoteHeader, NoteId, NoteIdAndArgs, NoteIdAndArgsArray, NoteInclusionProof, NoteLocation, NoteMetadata, NoteRecipient, NoteRecipientArray, NoteScript, NoteStorage, NoteSyncBlock, NoteSyncInfo, NoteTag, NoteType, OutputNote, OutputNoteArray, OutputNoteRecord, OutputNoteState, OutputNotes, Package, PartialNote, Poseidon2, ProcedureThreshold, Program, ProvenTransaction, PublicKey, RpcClient, Rpo256, SerializedInputNoteData, SerializedOutputNoteData, SerializedTransactionData, Signature, SigningInputs, SigningInputsType, SlotAndKeys, SparseMerklePath, StorageMap, StorageMapEntry, StorageMapEntryJs, StorageMapInfo, StorageMapUpdate, StorageSlot, StorageSlotArray, SyncSummary, TestUtils, TokenSymbol, TransactionArgs, TransactionFilter, TransactionId, TransactionProver, TransactionRecord, TransactionRequest, TransactionRequestBuilder, TransactionResult, TransactionScript, TransactionScriptInputPair, TransactionScriptInputPairArray, TransactionStatus, TransactionStoreUpdate, TransactionSummary, WebClient, WebKeystoreApi, Word, createAuthFalcon512RpoMultisig, exportStore, importStore, initSync, initThreadPool, mtProbeAsync, mtProbeSync, parallelSumBench, rayonThreadCount, sequentialSumBench, setupLogging, wbg_rayon_PoolBuilder, wbg_rayon_start_worker } from './Cargo-C9UbiAcT.js';
3
+
4
+ const WorkerAction = Object.freeze({
5
+ INIT: "init",
6
+ INIT_MOCK: "initMock",
7
+ INIT_THREAD_POOL: "initThreadPool",
8
+ CALL_METHOD: "callMethod",
9
+ EXECUTE_CALLBACK: "executeCallback",
10
+ });
11
+
12
+ const CallbackType = Object.freeze({
13
+ GET_KEY: "getKey",
14
+ INSERT_KEY: "insertKey",
15
+ SIGN: "sign",
16
+ });
17
+
18
+ const MethodName = Object.freeze({
19
+ CREATE_CLIENT: "createClient",
20
+ APPLY_TRANSACTION: "applyTransaction",
21
+ EXECUTE_TRANSACTION: "executeTransaction",
22
+ PROVE_TRANSACTION: "proveTransaction",
23
+ SUBMIT_NEW_TRANSACTION: "submitNewTransaction",
24
+ SUBMIT_NEW_TRANSACTION_MOCK: "submitNewTransactionMock",
25
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER: "submitNewTransactionWithProver",
26
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK: "submitNewTransactionWithProverMock",
27
+ SYNC_STATE: "syncState",
28
+ SYNC_STATE_MOCK: "syncStateMock",
29
+ SYNC_CHAIN: "syncChain",
30
+ SYNC_CHAIN_MOCK: "syncChainMock",
31
+ SYNC_NOTE_TRANSPORT: "syncNoteTransport",
32
+ SYNC_NOTE_TRANSPORT_MOCK: "syncNoteTransportMock",
33
+ });
34
+
35
+ /**
36
+ * Sync Lock Module
37
+ *
38
+ * Coordinates concurrent sync calls using the Web Locks API.
39
+ *
40
+ * Behavior:
41
+ * - Same-method coalescing: if a sync of the same method is in progress,
42
+ * subsequent callers share its result promise
43
+ * - Different-method serialization: different methods (e.g. syncState vs
44
+ * syncNoteTransport) wait for each other via the Web Lock, or via an
45
+ * in-process per-dbId promise chain when Web Locks are unavailable
46
+ * - Web Locks also serialize across tabs (Chrome 69+, Safari 15.4+)
47
+ */
48
+
49
+ /**
50
+ * Check if the Web Locks API is available.
51
+ */
52
+ function hasWebLocks() {
53
+ return (
54
+ typeof navigator !== "undefined" &&
55
+ navigator.locks !== undefined &&
56
+ typeof navigator.locks.request === "function"
57
+ );
58
+ }
59
+
60
+ // Coalesce map keyed by `${dbId}:${methodId}` -> in-flight promise.
61
+ const inFlight = new Map();
62
+
63
+ // Per-dbId promise tail used to serialize cross-method calls when Web Locks
64
+ // are unavailable. Each new task chains onto the current tail so different
65
+ // methods on the same dbId run sequentially within the tab.
66
+ const fallbackTails = new Map();
67
+
68
+ /**
69
+ * Build the coalesce-map key for an in-flight sync of `(dbId, methodId)`.
70
+ *
71
+ * @param {string} dbId
72
+ * @param {string} methodId
73
+ * @returns {string}
74
+ */
75
+ function coalesceKey(dbId, methodId) {
76
+ return `${dbId}:${methodId}`;
77
+ }
78
+
79
+ /**
80
+ * Run `fn` while holding the per-db Web Lock. When Web Locks are unavailable,
81
+ * serializes `fn` against any other in-flight call on the same `dbId` via an
82
+ * in-process promise chain — the wasm-bindgen `WebClient` uses a synchronous
83
+ * `RefCell` for interior mutability in the browser, so overlapping
84
+ * cross-method borrows would throw "recursive use of an object detected
85
+ * which would lead to unsafe aliasing in rust".
86
+ *
87
+ * @param {string} dbId
88
+ * @param {() => Promise<T>} fn
89
+ * @returns {Promise<T>}
90
+ * @template T
91
+ */
92
+ function runUnderLock(dbId, fn) {
93
+ if (!hasWebLocks()) {
94
+ const prev = fallbackTails.get(dbId) ?? Promise.resolve();
95
+ const next = prev.catch(() => {}).then(fn);
96
+ const guarded = next.catch(() => {});
97
+ fallbackTails.set(dbId, guarded);
98
+ guarded.then(() => {
99
+ // Drop the slot only if no successor chained onto this tail.
100
+ if (fallbackTails.get(dbId) === guarded) fallbackTails.delete(dbId);
101
+ });
102
+ return next;
103
+ }
104
+ return navigator.locks.request(
105
+ `miden-sync-${dbId}`,
106
+ { mode: "exclusive" },
107
+ fn
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Run `fn` under the sync lock for (dbId, methodId).
113
+ *
114
+ * Concurrent calls with the same (dbId, methodId) share the same promise
115
+ * (coalescing). Concurrent calls on the same dbId with different methodIds
116
+ * serialize via the Web Lock.
117
+ *
118
+ * @param {string} dbId - Database ID
119
+ * @param {string} methodId - Method identifier (see MethodName constants)
120
+ * @param {() => Promise<T>} fn - Work to run under the lock
121
+ * @returns {Promise<T>}
122
+ */
123
+ function withSyncLock(dbId, methodId, fn) {
124
+ const key = coalesceKey(dbId, methodId);
125
+
126
+ let work = inFlight.get(key);
127
+ if (!work) {
128
+ work = runUnderLock(dbId, fn);
129
+ inFlight.set(key, work);
130
+ // Swallow on the derived promise so a rejection here doesn't surface as
131
+ // an unhandled rejection; the caller still sees the error through `work`.
132
+ work
133
+ .finally(() => {
134
+ if (inFlight.get(key) === work) inFlight.delete(key);
135
+ })
136
+ .catch(() => {});
137
+ }
138
+
139
+ return work;
140
+ }
141
+
142
+ /**
143
+ * Shared utility functions for the MidenClient resource classes.
144
+ * Each function accepts a `wasm` parameter (the WASM module) for constructing typed objects.
145
+ */
146
+
147
+ /**
148
+ * Resolves an AccountRef (string | Account | AccountId) to an AccountId.
149
+ *
150
+ * - Strings starting with `0x`/`0X` are parsed as hex via `AccountId.fromHex()`.
151
+ * - Other strings are parsed as bech32 via `AccountId.fromBech32()`.
152
+ * - Objects with an `.id()` method (Account) are resolved by calling `.id()`.
153
+ * - Otherwise, the value is assumed to be an AccountId pass-through.
154
+ *
155
+ * @param {string | Account | AccountId} ref - The account reference to resolve.
156
+ * @param {object} wasm - The WASM module.
157
+ * @returns {AccountId} The resolved AccountId.
158
+ */
159
+ function resolveAccountRef(ref, wasm) {
160
+ if (ref == null) {
161
+ throw new Error("Account reference cannot be null or undefined");
162
+ }
163
+ if (typeof ref === "string") {
164
+ if (ref.startsWith("0x") || ref.startsWith("0X")) {
165
+ return wasm.AccountId.fromHex(ref);
166
+ }
167
+ return wasm.AccountId.fromBech32(ref);
168
+ }
169
+ if (ref && typeof ref.id === "function") {
170
+ return ref.id();
171
+ }
172
+ return ref;
173
+ }
174
+
175
+ /**
176
+ * Resolves an AccountRef to a WASM Address object.
177
+ *
178
+ * - Strings starting with bech32 prefixes (`m`) are parsed via `Address.fromBech32()`.
179
+ * - Strings starting with `0x`/`0X` are parsed as hex AccountId, then wrapped in Address.
180
+ * - Account objects are resolved via `.id()` then wrapped in Address.
181
+ * - AccountId objects are wrapped in Address directly.
182
+ *
183
+ * @param {string | Account | AccountId} ref - The account reference to resolve.
184
+ * @param {object} wasm - The WASM module.
185
+ * @returns {Address} The resolved Address.
186
+ */
187
+ function resolveAddress(ref, wasm) {
188
+ if (ref == null) {
189
+ throw new Error("Address reference cannot be null or undefined");
190
+ }
191
+ if (typeof ref === "string") {
192
+ if (ref.startsWith("0x") || ref.startsWith("0X")) {
193
+ const accountId = wasm.AccountId.fromHex(ref);
194
+ return wasm.Address.fromAccountId(accountId, undefined);
195
+ }
196
+ return wasm.Address.fromBech32(ref);
197
+ }
198
+ if (ref && typeof ref.id === "function") {
199
+ const accountId = ref.id();
200
+ return wasm.Address.fromAccountId(accountId, undefined);
201
+ }
202
+ return wasm.Address.fromAccountId(ref, undefined);
203
+ }
204
+
205
+ /**
206
+ * Resolves a NoteVisibility string to a WASM NoteType value.
207
+ *
208
+ * @param {string | undefined} type - "public" or "private". Defaults to "public".
209
+ * @param {object} wasm - The WASM module.
210
+ * @returns {number} The NoteType enum value.
211
+ */
212
+ function resolveNoteType(type, wasm) {
213
+ if (type === "private") {
214
+ return wasm.NoteType.Private;
215
+ }
216
+ if (type === "public" || type == null) {
217
+ return wasm.NoteType.Public;
218
+ }
219
+ throw new Error(
220
+ `Unknown note type: "${type}". Expected "public" or "private".`
221
+ );
222
+ }
223
+
224
+ /**
225
+ * Resolves a storage mode string to a WASM AccountStorageMode instance.
226
+ *
227
+ * @param {string | undefined} mode - "private", "public", or "network". Defaults to "private".
228
+ * @param {object} wasm - The WASM module.
229
+ * @returns {AccountStorageMode} The storage mode instance.
230
+ */
231
+ function resolveStorageMode(mode, wasm) {
232
+ switch (mode) {
233
+ case "public":
234
+ return wasm.AccountStorageMode.public();
235
+ case "network":
236
+ return wasm.AccountStorageMode.network();
237
+ case "private":
238
+ case undefined:
239
+ case null:
240
+ return wasm.AccountStorageMode.private();
241
+ default:
242
+ throw new Error(
243
+ `Unknown storage mode: "${mode}". Expected "private", "public", or "network".`
244
+ );
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Resolves an auth scheme string to a WASM AuthScheme enum value.
250
+ *
251
+ * @param {string | undefined} scheme - "falcon" or "ecdsa". Defaults to "falcon".
252
+ * @param {object} wasm - The WASM module.
253
+ * @returns {number} The AuthScheme enum value.
254
+ */
255
+ function resolveAuthScheme(scheme, wasm) {
256
+ if (scheme === "ecdsa") {
257
+ return wasm.AuthScheme.AuthEcdsaK256Keccak;
258
+ }
259
+ if (scheme === "falcon" || scheme == null) {
260
+ return wasm.AuthScheme.AuthRpoFalcon512;
261
+ }
262
+ throw new Error(
263
+ `Unknown auth scheme: "${scheme}". Expected "falcon" or "ecdsa".`
264
+ );
265
+ }
266
+
267
+ /**
268
+ * Resolves an AccountType value to a boolean `mutable` flag
269
+ * for the underlying WASM `newWallet()` / `importPublicAccountFromSeed()` calls.
270
+ *
271
+ * Accepts the numeric WASM enum values (2 = immutable, 3 = mutable) or the
272
+ * legacy string aliases ("MutableWallet", "ImmutableWallet"). Defaults to
273
+ * mutable when undefined.
274
+ *
275
+ * @param {number | string | undefined} accountType
276
+ * @returns {boolean} Whether the account code is mutable.
277
+ */
278
+ function resolveAccountMutability(accountType) {
279
+ if (
280
+ accountType == null ||
281
+ accountType === "MutableWallet" ||
282
+ accountType === 3
283
+ ) {
284
+ return true;
285
+ }
286
+ if (accountType === "ImmutableWallet" || accountType === 2) {
287
+ return false;
288
+ }
289
+ throw new Error(
290
+ `Unknown wallet account type: "${accountType}". Expected AccountType.MutableWallet (3) or AccountType.ImmutableWallet (2).`
291
+ );
292
+ }
293
+
294
+ /**
295
+ * Resolves a NoteInput (string | NoteId | InputNoteRecord | Note) to a hex string.
296
+ *
297
+ * - Strings are passed through unchanged.
298
+ * - NoteId WASM objects are converted via `.toString()`.
299
+ * - InputNoteRecord and Note objects (with an `.id()` method) are resolved via `.id().toString()`.
300
+ *
301
+ * @param {string | object} input - The note reference to resolve.
302
+ * @returns {string} The hex note ID string.
303
+ */
304
+ function resolveNoteIdHex(input) {
305
+ if (input == null) {
306
+ throw new Error("Note ID cannot be null or undefined");
307
+ }
308
+ if (typeof input === "string") {
309
+ return input;
310
+ }
311
+ // NoteId WASM object — has toString() but not id() (unlike InputNoteRecord/Note).
312
+ // Check for constructor.fromHex to distinguish from plain objects (which also inherit toString).
313
+ if (
314
+ typeof input.toString === "function" &&
315
+ typeof input.id !== "function" &&
316
+ input.constructor?.fromHex !== undefined
317
+ ) {
318
+ return input.toString();
319
+ }
320
+ // InputNoteRecord, Note, or other object with id() returning NoteId
321
+ if (typeof input.id === "function") {
322
+ return input.id().toString();
323
+ }
324
+ throw new TypeError(
325
+ `Cannot resolve note ID: expected string, NoteId, InputNoteRecord, or Note, got ${typeof input}`
326
+ );
327
+ }
328
+
329
+ /**
330
+ * Resolves a TransactionId reference (string | TransactionId) to a hex string.
331
+ *
332
+ * - Strings are passed through unchanged.
333
+ * - TransactionId WASM objects are converted via `.toHex()`.
334
+ *
335
+ * @param {string | object} input - The transaction ID reference to resolve.
336
+ * @returns {string} The hex transaction ID string.
337
+ */
338
+ function resolveTransactionIdHex(input) {
339
+ if (input == null) {
340
+ throw new Error("Transaction ID cannot be null or undefined");
341
+ }
342
+ if (typeof input === "string") {
343
+ return input;
344
+ }
345
+ // TransactionId WASM object — toHex() returns hex
346
+ if (typeof input.toHex === "function") {
347
+ return input.toHex();
348
+ }
349
+ throw new TypeError(
350
+ `Cannot resolve transaction ID: expected string or TransactionId, got ${typeof input}`
351
+ );
352
+ }
353
+
354
+ /**
355
+ * Hashes a seed value. Strings are hashed via SHA-256 to produce a 32-byte Uint8Array.
356
+ * Uint8Array values are passed through unchanged.
357
+ *
358
+ * @param {string | Uint8Array} seed - The seed to hash.
359
+ * @returns {Promise<Uint8Array>} The hashed seed.
360
+ */
361
+ async function hashSeed(seed) {
362
+ if (seed instanceof Uint8Array) {
363
+ return seed;
364
+ }
365
+ if (typeof seed === "string") {
366
+ const encoded = new TextEncoder().encode(seed);
367
+ const hash = await crypto.subtle.digest("SHA-256", encoded);
368
+ return new Uint8Array(hash);
369
+ }
370
+ throw new TypeError(
371
+ `Invalid seed type: expected string or Uint8Array, got ${typeof seed}`
372
+ );
373
+ }
374
+
375
+ class AccountsResource {
376
+ #inner;
377
+ #getWasm;
378
+ #client;
379
+
380
+ constructor(inner, getWasm, client) {
381
+ this.#inner = inner;
382
+ this.#getWasm = getWasm;
383
+ this.#client = client;
384
+ }
385
+
386
+ async create(opts) {
387
+ this.#client.assertNotTerminated();
388
+ const wasm = await this.#getWasm();
389
+
390
+ const type = opts?.type;
391
+
392
+ if (
393
+ type === 0 ||
394
+ type === 1 ||
395
+ type === "FungibleFaucet" ||
396
+ type === "NonFungibleFaucet"
397
+ ) {
398
+ const storageMode = resolveStorageMode(opts.storage ?? "public", wasm);
399
+ const authScheme = resolveAuthScheme(opts.auth, wasm);
400
+ return await this.#inner.newFaucet(
401
+ storageMode,
402
+ type === 1 || type === "NonFungibleFaucet",
403
+ opts.name ?? opts.symbol,
404
+ opts.symbol,
405
+ opts.decimals,
406
+ BigInt(opts.maxSupply),
407
+ authScheme
408
+ );
409
+ } else if (
410
+ type === "ImmutableContract" ||
411
+ type === "MutableContract" ||
412
+ opts?.components // Contracts are distinguished from wallets by having components
413
+ ) {
414
+ return await this.#createContract(opts, wasm);
415
+ } else {
416
+ // Default: wallet (mutable or immutable based on type)
417
+ const mutable = resolveAccountMutability(opts?.type);
418
+ const storageMode = resolveStorageMode(opts?.storage ?? "private", wasm);
419
+ const authScheme = resolveAuthScheme(opts?.auth, wasm);
420
+ const seed = opts?.seed ? await hashSeed(opts.seed) : undefined;
421
+ return await this.#inner.newWallet(
422
+ storageMode,
423
+ mutable,
424
+ authScheme,
425
+ seed
426
+ );
427
+ }
428
+ }
429
+
430
+ async #createContract(opts, wasm) {
431
+ if (!opts.seed)
432
+ throw new Error("Contract creation requires a 'seed' (Uint8Array)");
433
+ if (!opts.auth)
434
+ throw new Error("Contract creation requires an 'auth' (AuthSecretKey)");
435
+
436
+ // The 0.15 protocol has no code-mutability distinction, so the `type`
437
+ // ("ImmutableContract" / "MutableContract") only steers routing here; the
438
+ // account's on-chain visibility is set entirely by `storageMode`.
439
+ const storageMode = resolveStorageMode(opts.storage ?? "public", wasm);
440
+ const authComponent =
441
+ wasm.AccountComponent.createAuthComponentFromSecretKey(opts.auth);
442
+
443
+ // Schema commitment from `build()` is not a substitute for contract code; require explicit
444
+ // `components` so auth-only contracts are rejected at this layer.
445
+ const components = opts.components ?? [];
446
+ if (components.length === 0) {
447
+ throw new Error(
448
+ "Contract accounts require at least one non-auth procedure: pass at least one entry in `components`."
449
+ );
450
+ }
451
+
452
+ let builder = new wasm.AccountBuilder(opts.seed)
453
+ .storageMode(storageMode)
454
+ .withAuthComponent(authComponent);
455
+
456
+ for (const component of components) {
457
+ builder = builder.withComponent(component);
458
+ }
459
+
460
+ const built = builder.build();
461
+ const account = built.account;
462
+
463
+ await this.#inner.newAccountWithSecretKey(account, opts.auth);
464
+ return account;
465
+ }
466
+
467
+ async insert({ account, overwrite = false }) {
468
+ this.#client.assertNotTerminated();
469
+ await this.#inner.newAccount(account, overwrite);
470
+ }
471
+
472
+ async getOrImport(ref) {
473
+ this.#client.assertNotTerminated();
474
+ return (await this.get(ref)) ?? (await this.import(ref));
475
+ }
476
+
477
+ async get(ref) {
478
+ this.#client.assertNotTerminated();
479
+ const wasm = await this.#getWasm();
480
+ const id = resolveAccountRef(ref, wasm);
481
+ const account = await this.#inner.getAccount(id);
482
+ return account ?? null;
483
+ }
484
+
485
+ async list() {
486
+ this.#client.assertNotTerminated();
487
+ return await this.#inner.getAccounts();
488
+ }
489
+
490
+ async getDetails(ref) {
491
+ this.#client.assertNotTerminated();
492
+ const wasm = await this.#getWasm();
493
+ const id = resolveAccountRef(ref, wasm);
494
+ const account = await this.#inner.getAccount(id);
495
+ if (!account) {
496
+ throw new Error(`Account not found: ${id.toString()}`);
497
+ }
498
+ const keys = this.#inner.keystore
499
+ ? await this.#inner.keystore.getCommitments(id)
500
+ : await this.#inner.getPublicKeyCommitmentsOfAccount(id);
501
+ return {
502
+ account,
503
+ vault: account.vault(),
504
+ storage: account.storage(),
505
+ code: account.code() ?? null,
506
+ keys,
507
+ };
508
+ }
509
+
510
+ async getBalance(accountRef, tokenRef) {
511
+ this.#client.assertNotTerminated();
512
+ const wasm = await this.#getWasm();
513
+ const accountId = resolveAccountRef(accountRef, wasm);
514
+ const faucetId = resolveAccountRef(tokenRef, wasm);
515
+ const reader = await this.#inner.accountReader(accountId);
516
+ return await reader.getBalance(faucetId);
517
+ }
518
+
519
+ async import(input) {
520
+ this.#client.assertNotTerminated();
521
+ const wasm = await this.#getWasm();
522
+
523
+ // Early exit for string, Account, and AccountHeader types before property
524
+ // checks, preventing misrouting if a WASM object ever gains a .file or .seed
525
+ // property. Bare AccountId (no .id() method) falls through to the fallback.
526
+ if (typeof input === "string" || typeof input.id === "function") {
527
+ const id = resolveAccountRef(input, wasm);
528
+ await this.#inner.importAccountById(id);
529
+ return await this.#inner.getAccount(id);
530
+ }
531
+
532
+ if (input.file) {
533
+ // Extract accountId before importAccountFile — WASM consumes the
534
+ // AccountFile by value, invalidating the JS wrapper after the call.
535
+ const accountId =
536
+ typeof input.file.accountId === "function"
537
+ ? input.file.accountId()
538
+ : null;
539
+ await this.#inner.importAccountFile(input.file);
540
+ if (accountId) {
541
+ return await this.#inner.getAccount(accountId);
542
+ }
543
+ throw new Error(
544
+ "Could not determine account ID from AccountFile. " +
545
+ "Ensure the file contains a valid account."
546
+ );
547
+ }
548
+
549
+ if (input.seed) {
550
+ // Import public account from seed
551
+ const authScheme = resolveAuthScheme(input.auth, wasm);
552
+ const mutable = resolveAccountMutability(input.type);
553
+ return await this.#inner.importPublicAccountFromSeed(
554
+ input.seed,
555
+ mutable,
556
+ authScheme
557
+ );
558
+ }
559
+
560
+ // Fallback: treat as AccountRef (string, AccountId, Account, AccountHeader)
561
+ const id = resolveAccountRef(input, wasm);
562
+ await this.#inner.importAccountById(id);
563
+ return await this.#inner.getAccount(id);
564
+ }
565
+
566
+ async export(ref) {
567
+ this.#client.assertNotTerminated();
568
+ const wasm = await this.#getWasm();
569
+ const id = resolveAccountRef(ref, wasm);
570
+ return await this.#inner.exportAccountFile(id);
571
+ }
572
+
573
+ async addAddress(ref, addr) {
574
+ this.#client.assertNotTerminated();
575
+ const wasm = await this.#getWasm();
576
+ const id = resolveAccountRef(ref, wasm);
577
+ const address = wasm.Address.fromBech32(addr);
578
+ await this.#inner.insertAccountAddress(id, address);
579
+ }
580
+
581
+ async removeAddress(ref, addr) {
582
+ this.#client.assertNotTerminated();
583
+ const wasm = await this.#getWasm();
584
+ const id = resolveAccountRef(ref, wasm);
585
+ const address = wasm.Address.fromBech32(addr);
586
+ await this.#inner.removeAccountAddress(id, address);
587
+ }
588
+ }
589
+
590
+ class TransactionsResource {
591
+ #inner;
592
+ #getWasm;
593
+ #client;
594
+
595
+ constructor(inner, getWasm, client) {
596
+ this.#inner = inner;
597
+ this.#getWasm = getWasm;
598
+ this.#client = client;
599
+ }
600
+
601
+ async send(opts) {
602
+ this.#client.assertNotTerminated();
603
+ const wasm = await this.#getWasm();
604
+
605
+ if (opts.returnNote === true) {
606
+ // returnNote path — build the P2ID note in JS so we can return the Note
607
+ // object to the caller (e.g. for out-of-band delivery to the recipient).
608
+ if (opts.reclaimAfter != null || opts.timelockUntil != null) {
609
+ throw new Error(
610
+ "reclaimAfter and timelockUntil are not supported when returnNote is true"
611
+ );
612
+ }
613
+
614
+ const senderId = resolveAccountRef(opts.account, wasm);
615
+ const receiverId = resolveAccountRef(opts.to, wasm);
616
+ const faucetId = resolveAccountRef(opts.token, wasm);
617
+ const noteType = resolveNoteType(opts.type, wasm);
618
+
619
+ const note = wasm.Note.createP2IDNote(
620
+ senderId,
621
+ receiverId,
622
+ new wasm.NoteAssets([
623
+ new wasm.FungibleAsset(faucetId, BigInt(opts.amount)),
624
+ ]),
625
+ noteType,
626
+ new wasm.NoteAttachment()
627
+ );
628
+
629
+ // NoteArray constructor consumes its elements; use push(&note) to keep
630
+ // `note` valid so we can return it to the caller below.
631
+ const ownOutputs = new wasm.NoteArray();
632
+ ownOutputs.push(note);
633
+ const request = new wasm.TransactionRequestBuilder()
634
+ .withOwnOutputNotes(ownOutputs)
635
+ .build();
636
+
637
+ const { txId, result } = await this.#submitOrSubmitWithProver(
638
+ senderId,
639
+ request,
640
+ opts.prover
641
+ );
642
+
643
+ if (opts.waitForConfirmation) {
644
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
645
+ }
646
+
647
+ return { txId, note, result };
648
+ }
649
+
650
+ // Default path — note built in WASM with optional reclaim/timelock
651
+ const { accountId, request } = await this.#buildSendRequest(opts, wasm);
652
+ const { txId, result } = await this.#submitOrSubmitWithProver(
653
+ accountId,
654
+ request,
655
+ opts.prover
656
+ );
657
+
658
+ if (opts.waitForConfirmation) {
659
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
660
+ }
661
+
662
+ return { txId, note: null, result };
663
+ }
664
+
665
+ async mint(opts) {
666
+ this.#client.assertNotTerminated();
667
+ const wasm = await this.#getWasm();
668
+ const { accountId, request } = await this.#buildMintRequest(opts, wasm);
669
+
670
+ const { txId, result } = await this.#submitOrSubmitWithProver(
671
+ accountId,
672
+ request,
673
+ opts.prover
674
+ );
675
+
676
+ if (opts.waitForConfirmation) {
677
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
678
+ }
679
+
680
+ return { txId, result };
681
+ }
682
+
683
+ async consume(opts) {
684
+ this.#client.assertNotTerminated();
685
+ const wasm = await this.#getWasm();
686
+ const { accountId, request } = await this.#buildConsumeRequest(opts, wasm);
687
+
688
+ const { txId, result } = await this.#submitOrSubmitWithProver(
689
+ accountId,
690
+ request,
691
+ opts.prover
692
+ );
693
+
694
+ if (opts.waitForConfirmation) {
695
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
696
+ }
697
+
698
+ return { txId, result };
699
+ }
700
+
701
+ async consumeAll(opts) {
702
+ this.#client.assertNotTerminated();
703
+ const wasm = await this.#getWasm();
704
+
705
+ // getConsumableNotes takes AccountId by value (consumed by WASM).
706
+ // Save hex so we can reconstruct for submitNewTransaction.
707
+ const accountId = resolveAccountRef(opts.account, wasm);
708
+ const accountIdHex = accountId.toString();
709
+ const consumable = await this.#inner.getConsumableNotes(accountId);
710
+
711
+ if (!consumable || consumable.length === 0) {
712
+ return { txId: null, consumed: 0, remaining: 0 };
713
+ }
714
+
715
+ const total = consumable.length;
716
+ const toConsume =
717
+ opts.maxNotes != null ? consumable.slice(0, opts.maxNotes) : consumable;
718
+
719
+ if (toConsume.length === 0) {
720
+ return { txId: null, consumed: 0, remaining: total };
721
+ }
722
+
723
+ const notes = toConsume.map((c) => c.inputNoteRecord().toNote());
724
+
725
+ const request = await this.#inner.newConsumeTransactionRequest(notes);
726
+
727
+ const { txId, result } = await this.#submitOrSubmitWithProver(
728
+ wasm.AccountId.fromHex(accountIdHex),
729
+ request,
730
+ opts.prover
731
+ );
732
+
733
+ if (opts.waitForConfirmation) {
734
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
735
+ }
736
+
737
+ return {
738
+ txId,
739
+ consumed: toConsume.length,
740
+ remaining: total - toConsume.length,
741
+ result,
742
+ };
743
+ }
744
+
745
+ async swap(opts) {
746
+ this.#client.assertNotTerminated();
747
+ const wasm = await this.#getWasm();
748
+ const { accountId, request } = await this.#buildSwapRequest(opts, wasm);
749
+
750
+ const { txId, result } = await this.#submitOrSubmitWithProver(
751
+ accountId,
752
+ request,
753
+ opts.prover
754
+ );
755
+
756
+ if (opts.waitForConfirmation) {
757
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
758
+ }
759
+
760
+ return { txId, result };
761
+ }
762
+
763
+ /** Create a partial-swap (PSWAP) note. See {@link PswapCreateOptions}. */
764
+ async pswapCreate(opts) {
765
+ this.#client.assertNotTerminated();
766
+ const wasm = await this.#getWasm();
767
+ const { accountId, request } = await this.#buildPswapCreateRequest(
768
+ opts,
769
+ wasm
770
+ );
771
+
772
+ const { txId, result } = await this.#submitOrSubmitWithProver(
773
+ accountId,
774
+ request,
775
+ opts.prover
776
+ );
777
+
778
+ if (opts.waitForConfirmation) {
779
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
780
+ }
781
+
782
+ return { txId, result };
783
+ }
784
+
785
+ /** Consume (fully or partially fill) a PSWAP note. See {@link PswapConsumeOptions}. */
786
+ async pswapConsume(opts) {
787
+ this.#client.assertNotTerminated();
788
+ const wasm = await this.#getWasm();
789
+ const { accountId, request } = await this.#buildPswapConsumeRequest(
790
+ opts,
791
+ wasm
792
+ );
793
+
794
+ const { txId, result } = await this.#submitOrSubmitWithProver(
795
+ accountId,
796
+ request,
797
+ opts.prover
798
+ );
799
+
800
+ if (opts.waitForConfirmation) {
801
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
802
+ }
803
+
804
+ return { txId, result };
805
+ }
806
+
807
+ /** Cancel a PSWAP note as its creator and reclaim the offered asset. See {@link PswapCancelOptions}. */
808
+ async pswapCancel(opts) {
809
+ this.#client.assertNotTerminated();
810
+ const wasm = await this.#getWasm();
811
+ const { accountId, request } = await this.#buildPswapCancelRequest(
812
+ opts,
813
+ wasm
814
+ );
815
+
816
+ const { txId, result } = await this.#submitOrSubmitWithProver(
817
+ accountId,
818
+ request,
819
+ opts.prover
820
+ );
821
+
822
+ if (opts.waitForConfirmation) {
823
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
824
+ }
825
+
826
+ return { txId, result };
827
+ }
828
+
829
+ async preview(opts) {
830
+ this.#client.assertNotTerminated();
831
+ const wasm = await this.#getWasm();
832
+
833
+ let accountId;
834
+ let request;
835
+
836
+ switch (opts.operation) {
837
+ case "send": {
838
+ ({ accountId, request } = await this.#buildSendRequest(opts, wasm));
839
+ break;
840
+ }
841
+ case "mint": {
842
+ ({ accountId, request } = await this.#buildMintRequest(opts, wasm));
843
+ break;
844
+ }
845
+ case "consume": {
846
+ ({ accountId, request } = await this.#buildConsumeRequest(opts, wasm));
847
+ break;
848
+ }
849
+ case "swap": {
850
+ ({ accountId, request } = await this.#buildSwapRequest(opts, wasm));
851
+ break;
852
+ }
853
+ case "pswapCreate": {
854
+ ({ accountId, request } = await this.#buildPswapCreateRequest(
855
+ opts,
856
+ wasm
857
+ ));
858
+ break;
859
+ }
860
+ case "pswapConsume": {
861
+ ({ accountId, request } = await this.#buildPswapConsumeRequest(
862
+ opts,
863
+ wasm
864
+ ));
865
+ break;
866
+ }
867
+ case "pswapCancel": {
868
+ ({ accountId, request } = await this.#buildPswapCancelRequest(
869
+ opts,
870
+ wasm
871
+ ));
872
+ break;
873
+ }
874
+ case "custom": {
875
+ accountId = resolveAccountRef(opts.account, wasm);
876
+ request = opts.request;
877
+ break;
878
+ }
879
+ default:
880
+ throw new Error(`Unknown preview operation: ${opts.operation}`);
881
+ }
882
+
883
+ return await this.#inner.executeForSummary(accountId, request);
884
+ }
885
+
886
+ async execute(opts) {
887
+ this.#client.assertNotTerminated();
888
+ const wasm = await this.#getWasm();
889
+ const accountId = resolveAccountRef(opts.account, wasm);
890
+
891
+ let builder = new wasm.TransactionRequestBuilder().withCustomScript(
892
+ opts.script
893
+ );
894
+
895
+ if (opts.foreignAccounts?.length) {
896
+ const accounts = opts.foreignAccounts.map((fa) => {
897
+ // Distinguish { id: AccountRef, storage? } wrapper objects from WASM types
898
+ // (Account/AccountHeader expose .id() as a method, wrappers have .id as a property)
899
+ const isWrapper =
900
+ fa !== null &&
901
+ typeof fa === "object" &&
902
+ "id" in fa &&
903
+ typeof fa.id !== "function";
904
+ const id = resolveAccountRef(isWrapper ? fa.id : fa, wasm);
905
+ const storage =
906
+ isWrapper && fa.storage
907
+ ? fa.storage
908
+ : new wasm.AccountStorageRequirements();
909
+ return wasm.ForeignAccount.public(id, storage);
910
+ });
911
+ builder = builder.withForeignAccounts(
912
+ new wasm.ForeignAccountArray(accounts)
913
+ );
914
+ }
915
+
916
+ const request = builder.build();
917
+ const { txId, result } = await this.#submitOrSubmitWithProver(
918
+ accountId,
919
+ request,
920
+ opts.prover
921
+ );
922
+
923
+ if (opts.waitForConfirmation) {
924
+ await this.waitFor(txId.toHex(), { timeout: opts.timeout });
925
+ }
926
+
927
+ return { txId, result };
928
+ }
929
+
930
+ async executeProgram(opts) {
931
+ this.#client.assertNotTerminated();
932
+ const wasm = await this.#getWasm();
933
+ const accountId = resolveAccountRef(opts.account, wasm);
934
+
935
+ let foreignAccountsArray = new wasm.ForeignAccountArray();
936
+ if (opts.foreignAccounts?.length) {
937
+ const accounts = opts.foreignAccounts.map((fa) => {
938
+ const isWrapper =
939
+ fa !== null &&
940
+ typeof fa === "object" &&
941
+ "id" in fa &&
942
+ typeof fa.id !== "function";
943
+ const id = resolveAccountRef(isWrapper ? fa.id : fa, wasm);
944
+ const storage =
945
+ isWrapper && fa.storage
946
+ ? fa.storage
947
+ : new wasm.AccountStorageRequirements();
948
+ return wasm.ForeignAccount.public(id, storage);
949
+ });
950
+ foreignAccountsArray = new wasm.ForeignAccountArray(accounts);
951
+ }
952
+
953
+ return await this.#inner.executeProgram(
954
+ accountId,
955
+ opts.script,
956
+ opts.adviceInputs ?? new wasm.AdviceInputs(),
957
+ foreignAccountsArray
958
+ );
959
+ }
960
+
961
+ async submit(account, request, opts) {
962
+ this.#client.assertNotTerminated();
963
+ const wasm = await this.#getWasm();
964
+ const accountId = resolveAccountRef(account, wasm);
965
+ return await this.#submitOrSubmitWithProver(
966
+ accountId,
967
+ request,
968
+ opts?.prover
969
+ );
970
+ }
971
+
972
+ async list(query) {
973
+ this.#client.assertNotTerminated();
974
+ const wasm = await this.#getWasm();
975
+
976
+ let filter;
977
+ if (!query) {
978
+ filter = wasm.TransactionFilter.all();
979
+ } else if (query.status === "uncommitted") {
980
+ filter = wasm.TransactionFilter.uncommitted();
981
+ } else if (query.ids) {
982
+ const txIds = query.ids.map((id) =>
983
+ wasm.TransactionId.fromHex(resolveTransactionIdHex(id))
984
+ );
985
+ filter = wasm.TransactionFilter.ids(txIds);
986
+ } else if (query.expiredBefore !== undefined) {
987
+ filter = wasm.TransactionFilter.expiredBefore(query.expiredBefore);
988
+ } else {
989
+ filter = wasm.TransactionFilter.all();
990
+ }
991
+
992
+ return await this.#inner.getTransactions(filter);
993
+ }
994
+
995
+ /**
996
+ * Polls for transaction confirmation.
997
+ *
998
+ * @param {string | TransactionId} txId - Transaction ID hex string or TransactionId object.
999
+ * @param {WaitOptions} [opts] - Polling options.
1000
+ * @param {number} [opts.timeout=60000] - Wall-clock polling timeout in
1001
+ * milliseconds. This is NOT a block height — it controls how long the
1002
+ * client waits before giving up. Set to 0 to disable the timeout and poll
1003
+ * indefinitely until the transaction is committed or discarded.
1004
+ * @param {number} [opts.interval=5000] - Polling interval in ms.
1005
+ * @param {function} [opts.onProgress] - Called with the current status on
1006
+ * each poll iteration ("pending", "submitted", or "committed").
1007
+ */
1008
+ async waitFor(txId, opts) {
1009
+ this.#client.assertNotTerminated();
1010
+ const hex = resolveTransactionIdHex(txId);
1011
+ const timeout = opts?.timeout ?? 60_000;
1012
+ const interval = opts?.interval ?? 5_000;
1013
+ const start = Date.now();
1014
+
1015
+ const wasm = await this.#getWasm();
1016
+
1017
+ while (true) {
1018
+ const elapsed = Date.now() - start;
1019
+ if (timeout > 0 && elapsed >= timeout) {
1020
+ throw new Error(
1021
+ `Transaction confirmation timed out after ${timeout}ms`
1022
+ );
1023
+ }
1024
+
1025
+ try {
1026
+ // Chain-only sync is sufficient: confirmation only needs on-chain
1027
+ // state, and skipping NTL keeps polling alive when the note
1028
+ // transport endpoint is unavailable.
1029
+ await this.#inner.syncChain();
1030
+ } catch {
1031
+ // Sync may fail transiently; continue polling
1032
+ }
1033
+
1034
+ // Recreate filter each iteration — WASM consumes it by value
1035
+ const filter = wasm.TransactionFilter.ids([
1036
+ wasm.TransactionId.fromHex(hex),
1037
+ ]);
1038
+ const txs = await this.#inner.getTransactions(filter);
1039
+
1040
+ if (txs && txs.length > 0) {
1041
+ const tx = txs[0];
1042
+ const status = tx.transactionStatus?.();
1043
+
1044
+ if (status) {
1045
+ if (status.isCommitted()) {
1046
+ opts?.onProgress?.("committed");
1047
+ return;
1048
+ }
1049
+ if (status.isDiscarded()) {
1050
+ throw new Error(`Transaction rejected: ${hex}`);
1051
+ }
1052
+ }
1053
+
1054
+ opts?.onProgress?.("submitted");
1055
+ } else {
1056
+ opts?.onProgress?.("pending");
1057
+ }
1058
+
1059
+ await new Promise((resolve) => setTimeout(resolve, interval));
1060
+ }
1061
+ }
1062
+
1063
+ // ── Shared request builders ──
1064
+
1065
+ async #buildSendRequest(opts, wasm) {
1066
+ const accountId = resolveAccountRef(opts.account, wasm);
1067
+ const targetId = resolveAccountRef(opts.to, wasm);
1068
+ const faucetId = resolveAccountRef(opts.token, wasm);
1069
+ const noteType = resolveNoteType(opts.type, wasm);
1070
+ const amount = BigInt(opts.amount);
1071
+
1072
+ const request = await this.#inner.newSendTransactionRequest(
1073
+ accountId,
1074
+ targetId,
1075
+ faucetId,
1076
+ noteType,
1077
+ amount,
1078
+ opts.reclaimAfter,
1079
+ opts.timelockUntil
1080
+ );
1081
+ return { accountId, request };
1082
+ }
1083
+
1084
+ async #buildMintRequest(opts, wasm) {
1085
+ const accountId = resolveAccountRef(opts.account, wasm);
1086
+ const targetId = resolveAccountRef(opts.to, wasm);
1087
+ const noteType = resolveNoteType(opts.type, wasm);
1088
+ const amount = BigInt(opts.amount);
1089
+
1090
+ // WASM signature: newMintTransactionRequest(target, faucet, noteType, amount)
1091
+ const request = await this.#inner.newMintTransactionRequest(
1092
+ targetId,
1093
+ accountId,
1094
+ noteType,
1095
+ amount
1096
+ );
1097
+ return { accountId, request };
1098
+ }
1099
+
1100
+ async #buildConsumeRequest(opts, wasm) {
1101
+ const accountId = resolveAccountRef(opts.account, wasm);
1102
+ const noteInputs = Array.isArray(opts.notes) ? opts.notes : [opts.notes];
1103
+
1104
+ const isDirectNote = (input) =>
1105
+ input !== null &&
1106
+ typeof input === "object" &&
1107
+ typeof input.id === "function" &&
1108
+ typeof input.toNote !== "function";
1109
+
1110
+ const hasDirectNotes = noteInputs.some(isDirectNote);
1111
+
1112
+ if (hasDirectNotes) {
1113
+ // At least one raw Note object — use NoteAndArgs builder path
1114
+ // (the only WASM path that accepts unauthenticated notes not in the store).
1115
+ const resolvedNotes = await Promise.all(
1116
+ noteInputs.map(async (input) => {
1117
+ if (isDirectNote(input)) return input;
1118
+ if (input && typeof input.toNote === "function")
1119
+ return input.toNote();
1120
+ return await this.#resolveNoteInput(input);
1121
+ })
1122
+ );
1123
+
1124
+ const noteAndArgsArr = resolvedNotes.map(
1125
+ (note) => new wasm.NoteAndArgs(note, null)
1126
+ );
1127
+ const request = new wasm.TransactionRequestBuilder()
1128
+ .withInputNotes(new wasm.NoteAndArgsArray(noteAndArgsArr))
1129
+ .build();
1130
+ return { accountId, request };
1131
+ }
1132
+
1133
+ // Standard path: all inputs are IDs or records — look up from store.
1134
+ const notes = await Promise.all(
1135
+ noteInputs.map((input) => this.#resolveNoteInput(input))
1136
+ );
1137
+ const request = await this.#inner.newConsumeTransactionRequest(notes);
1138
+ return { accountId, request };
1139
+ }
1140
+
1141
+ async #buildSwapRequest(opts, wasm) {
1142
+ const accountId = resolveAccountRef(opts.account, wasm);
1143
+ const offeredFaucetId = resolveAccountRef(opts.offer.token, wasm);
1144
+ const requestedFaucetId = resolveAccountRef(opts.request.token, wasm);
1145
+ const noteType = resolveNoteType(opts.type, wasm);
1146
+ const paybackNoteType = resolveNoteType(
1147
+ opts.paybackType ?? opts.type,
1148
+ wasm
1149
+ );
1150
+
1151
+ const request = await this.#inner.newSwapTransactionRequest(
1152
+ accountId,
1153
+ offeredFaucetId,
1154
+ BigInt(opts.offer.amount),
1155
+ requestedFaucetId,
1156
+ BigInt(opts.request.amount),
1157
+ noteType,
1158
+ paybackNoteType
1159
+ );
1160
+ return { accountId, request };
1161
+ }
1162
+
1163
+ async #buildPswapCreateRequest(opts, wasm) {
1164
+ const accountId = resolveAccountRef(opts.account, wasm);
1165
+ const offeredFaucetId = resolveAccountRef(opts.offer.token, wasm);
1166
+ const requestedFaucetId = resolveAccountRef(opts.request.token, wasm);
1167
+ const noteType = resolveNoteType(opts.type, wasm);
1168
+ const paybackNoteType = resolveNoteType(
1169
+ opts.paybackType ?? opts.type,
1170
+ wasm
1171
+ );
1172
+
1173
+ const request = await this.#inner.newPswapCreateTransactionRequest(
1174
+ accountId,
1175
+ offeredFaucetId,
1176
+ BigInt(opts.offer.amount),
1177
+ requestedFaucetId,
1178
+ BigInt(opts.request.amount),
1179
+ noteType,
1180
+ paybackNoteType
1181
+ );
1182
+ return { accountId, request };
1183
+ }
1184
+
1185
+ async #buildPswapConsumeRequest(opts, wasm) {
1186
+ const accountId = resolveAccountRef(opts.account, wasm);
1187
+ const note = await this.#resolveNoteInput(opts.note);
1188
+ const noteFillAmount = opts.noteFillAmount ?? 0n;
1189
+
1190
+ const request = await this.#inner.newPswapConsumeTransactionRequest(
1191
+ note,
1192
+ accountId,
1193
+ BigInt(opts.fillAmount),
1194
+ BigInt(noteFillAmount)
1195
+ );
1196
+ return { accountId, request };
1197
+ }
1198
+
1199
+ async #buildPswapCancelRequest(opts, wasm) {
1200
+ const accountId = resolveAccountRef(opts.account, wasm);
1201
+ const note = await this.#resolveNoteInput(opts.note);
1202
+
1203
+ const request = await this.#inner.newPswapCancelTransactionRequest(
1204
+ note,
1205
+ accountId
1206
+ );
1207
+ return { accountId, request };
1208
+ }
1209
+
1210
+ async #resolveNoteInput(input) {
1211
+ if (typeof input === "string") {
1212
+ const record = await this.#inner.getInputNote(input);
1213
+ if (!record) {
1214
+ throw new Error(`Note not found: ${input}`);
1215
+ }
1216
+ return record.toNote();
1217
+ }
1218
+ // InputNoteRecord — unwrap to Note
1219
+ if (input && typeof input.toNote === "function") {
1220
+ return input.toNote();
1221
+ }
1222
+ // NoteId — has toString() but not toNote() or id() (unlike InputNoteRecord/Note).
1223
+ // Check for constructor.fromHex to distinguish from plain objects.
1224
+ if (
1225
+ input &&
1226
+ typeof input.toString === "function" &&
1227
+ typeof input.toNote !== "function" &&
1228
+ typeof input.id !== "function" &&
1229
+ input.constructor?.fromHex !== undefined
1230
+ ) {
1231
+ const hex = input.toString();
1232
+ const record = await this.#inner.getInputNote(hex);
1233
+ if (!record) {
1234
+ throw new Error(`Note not found: ${hex}`);
1235
+ }
1236
+ return record.toNote();
1237
+ }
1238
+ // Assume it's already a Note object
1239
+ return input;
1240
+ }
1241
+
1242
+ async #submitOrSubmitWithProver(accountId, request, perCallProver) {
1243
+ const result = await this.#inner.executeTransaction(accountId, request);
1244
+ const prover = perCallProver ?? this.#client.defaultProver;
1245
+ const proven = prover
1246
+ ? await this.#inner.proveTransaction(result, prover)
1247
+ : await this.#inner.proveTransaction(result);
1248
+ const txId = result.id();
1249
+ const height = await this.#inner.submitProvenTransaction(proven, result);
1250
+ await this.#inner.applyTransaction(result, height);
1251
+ return { txId, result };
1252
+ }
1253
+ }
1254
+
1255
+ class NotesResource {
1256
+ #inner;
1257
+ #getWasm;
1258
+ #client;
1259
+
1260
+ constructor(inner, getWasm, client) {
1261
+ this.#inner = inner;
1262
+ this.#getWasm = getWasm;
1263
+ this.#client = client;
1264
+ }
1265
+
1266
+ async list(query) {
1267
+ this.#client.assertNotTerminated();
1268
+ const wasm = await this.#getWasm();
1269
+ const filter = buildNoteFilter(query, wasm);
1270
+ return await this.#inner.getInputNotes(filter);
1271
+ }
1272
+
1273
+ async get(noteId) {
1274
+ this.#client.assertNotTerminated();
1275
+ const result = await this.#inner.getInputNote(resolveNoteIdHex(noteId));
1276
+ return result ?? null;
1277
+ }
1278
+
1279
+ async listSent(query) {
1280
+ this.#client.assertNotTerminated();
1281
+ const wasm = await this.#getWasm();
1282
+ const filter = buildNoteFilter(query, wasm);
1283
+ return await this.#inner.getOutputNotes(filter);
1284
+ }
1285
+
1286
+ async listAvailable(opts) {
1287
+ this.#client.assertNotTerminated();
1288
+ const wasm = await this.#getWasm();
1289
+ const accountId = resolveAccountRef(opts.account, wasm);
1290
+ const consumable = await this.#inner.getConsumableNotes(accountId);
1291
+ return consumable.map((c) => c.inputNoteRecord());
1292
+ }
1293
+
1294
+ async import(noteFile) {
1295
+ this.#client.assertNotTerminated();
1296
+ return await this.#inner.importNoteFile(noteFile);
1297
+ }
1298
+
1299
+ async export(noteId, opts) {
1300
+ this.#client.assertNotTerminated();
1301
+ const wasm = await this.#getWasm();
1302
+ const format = opts?.format ?? wasm.NoteExportFormat.Full;
1303
+ return await this.#inner.exportNoteFile(resolveNoteIdHex(noteId), format);
1304
+ }
1305
+
1306
+ async fetchPrivate(opts) {
1307
+ this.#client.assertNotTerminated();
1308
+ if (opts?.mode === "all") {
1309
+ await this.#inner.fetchAllPrivateNotes();
1310
+ } else {
1311
+ await this.#inner.fetchPrivateNotes();
1312
+ }
1313
+ }
1314
+
1315
+ async sendPrivate(opts) {
1316
+ this.#client.assertNotTerminated();
1317
+ const wasm = await this.#getWasm();
1318
+
1319
+ let note;
1320
+ const input = opts.note;
1321
+ // Check if input is a Note object (has .id() and .assets() but not .toNote())
1322
+ if (
1323
+ input &&
1324
+ typeof input === "object" &&
1325
+ typeof input.id === "function" &&
1326
+ typeof input.assets === "function" &&
1327
+ typeof input.toNote !== "function"
1328
+ ) {
1329
+ note = input;
1330
+ } else {
1331
+ const noteHex = resolveNoteIdHex(input);
1332
+ const noteRecord = await this.#inner.getInputNote(noteHex);
1333
+ if (!noteRecord) {
1334
+ throw new Error(`Note not found: ${noteHex}`);
1335
+ }
1336
+ note = noteRecord.toNote();
1337
+ }
1338
+
1339
+ const address = resolveAddress(opts.to, wasm);
1340
+ await this.#inner.sendPrivateNote(note, address);
1341
+ }
1342
+ }
1343
+
1344
+ function buildNoteFilter(query, wasm) {
1345
+ if (!query) {
1346
+ return new wasm.NoteFilter(wasm.NoteFilterTypes.All, undefined);
1347
+ }
1348
+
1349
+ if (query.ids) {
1350
+ const noteIds = query.ids.map((id) =>
1351
+ wasm.NoteId.fromHex(resolveNoteIdHex(id))
1352
+ );
1353
+ return new wasm.NoteFilter(wasm.NoteFilterTypes.List, noteIds);
1354
+ }
1355
+
1356
+ if (query.status) {
1357
+ const statusMap = {
1358
+ consumed: wasm.NoteFilterTypes.Consumed,
1359
+ committed: wasm.NoteFilterTypes.Committed,
1360
+ expected: wasm.NoteFilterTypes.Expected,
1361
+ processing: wasm.NoteFilterTypes.Processing,
1362
+ unverified: wasm.NoteFilterTypes.Unverified,
1363
+ };
1364
+ const filterType = statusMap[query.status];
1365
+ if (filterType === undefined) {
1366
+ throw new Error(`Unknown note status: ${query.status}`);
1367
+ }
1368
+ return new wasm.NoteFilter(filterType, undefined);
1369
+ }
1370
+
1371
+ return new wasm.NoteFilter(wasm.NoteFilterTypes.All, undefined);
1372
+ }
1373
+
1374
+ class TagsResource {
1375
+ #inner;
1376
+ #client;
1377
+
1378
+ constructor(inner, getWasm, client) {
1379
+ this.#inner = inner;
1380
+ this.#client = client;
1381
+ }
1382
+
1383
+ async add(tag) {
1384
+ this.#client.assertNotTerminated();
1385
+ await this.#inner.addTag(String(tag));
1386
+ }
1387
+
1388
+ async remove(tag) {
1389
+ this.#client.assertNotTerminated();
1390
+ await this.#inner.removeTag(String(tag));
1391
+ }
1392
+
1393
+ async list() {
1394
+ this.#client.assertNotTerminated();
1395
+ const tags = await this.#inner.listTags();
1396
+ return Array.from(tags).map((t) => {
1397
+ const n = Number(t);
1398
+ if (Number.isNaN(n)) {
1399
+ throw new Error(`Invalid tag value: ${t}`);
1400
+ }
1401
+ return n;
1402
+ });
1403
+ }
1404
+ }
1405
+
1406
+ class SettingsResource {
1407
+ #inner;
1408
+ #client;
1409
+
1410
+ constructor(inner, _getWasm, client) {
1411
+ this.#inner = inner;
1412
+ this.#client = client;
1413
+ }
1414
+
1415
+ async get(key) {
1416
+ this.#client.assertNotTerminated();
1417
+ const value = await this.#inner.getSetting(key);
1418
+ return value === undefined ? null : value;
1419
+ }
1420
+
1421
+ async set(key, value) {
1422
+ this.#client.assertNotTerminated();
1423
+ await this.#inner.setSetting(key, value);
1424
+ }
1425
+
1426
+ async remove(key) {
1427
+ this.#client.assertNotTerminated();
1428
+ await this.#inner.removeSetting(key);
1429
+ }
1430
+
1431
+ async listKeys() {
1432
+ this.#client.assertNotTerminated();
1433
+ return await this.#inner.listSettingKeys();
1434
+ }
1435
+ }
1436
+
1437
+ class CompilerResource {
1438
+ #inner;
1439
+ #getWasm;
1440
+ #client;
1441
+
1442
+ constructor(inner, getWasm, client = null) {
1443
+ this.#inner = inner;
1444
+ this.#getWasm = getWasm;
1445
+ this.#client = client;
1446
+ }
1447
+
1448
+ /**
1449
+ * Compiles MASM code + slots into an AccountComponent ready for accounts.create().
1450
+ *
1451
+ * @param {{ code: string, slots: StorageSlot[], supportAllTypes?: boolean }} opts
1452
+ * @returns {Promise<AccountComponent>}
1453
+ */
1454
+ async component({ code, slots = [], supportAllTypes = true }) {
1455
+ this.#client?.assertNotTerminated();
1456
+ const wasm = await this.#getWasm();
1457
+ const builder = await this.#inner.createCodeBuilder();
1458
+ const compiled = builder.compileAccountComponentCode(code);
1459
+ const component = wasm.AccountComponent.compile(compiled, slots);
1460
+ return supportAllTypes ? component.withSupportsAllTypes() : component;
1461
+ }
1462
+
1463
+ /**
1464
+ * Compiles a transaction script, optionally linking named libraries inline.
1465
+ *
1466
+ * @param {{ code: string, libraries?: Array<{ namespace: string, code: string, linking?: "dynamic" | "static" }> }} opts
1467
+ * @returns {Promise<TransactionScript>}
1468
+ */
1469
+ async txScript({ code, libraries = [] }) {
1470
+ this.#client?.assertNotTerminated();
1471
+ // Ensure WASM is initialized (result unused — only #inner needs it)
1472
+ await this.#getWasm();
1473
+ const builder = await this.#inner.createCodeBuilder();
1474
+ linkLibraries(builder, libraries);
1475
+ return builder.compileTxScript(code);
1476
+ }
1477
+
1478
+ /**
1479
+ * Compiles a note script, optionally linking named libraries inline.
1480
+ *
1481
+ * @param {{ code: string, libraries?: Array<{ namespace: string, code: string, linking?: "dynamic" | "static" }> }} opts
1482
+ * @returns {Promise<NoteScript>}
1483
+ */
1484
+ async noteScript({ code, libraries = [] }) {
1485
+ this.#client?.assertNotTerminated();
1486
+ await this.#getWasm();
1487
+ const builder = await this.#inner.createCodeBuilder();
1488
+ linkLibraries(builder, libraries);
1489
+ return builder.compileNoteScript(code);
1490
+ }
1491
+ }
1492
+
1493
+ // Builds and links each library entry against `builder`. Inline
1494
+ // `{ namespace, code, linking? }` entries are built via `buildLibrary` and
1495
+ // linked according to `linking` (defaulting to dynamic, matching tutorial
1496
+ // behavior). Pre-built library objects are linked dynamically.
1497
+ function linkLibraries(builder, libraries) {
1498
+ for (const lib of libraries) {
1499
+ if (lib && typeof lib.namespace === "string") {
1500
+ const built = builder.buildLibrary(lib.namespace, lib.code);
1501
+ if (lib.linking === "static") {
1502
+ builder.linkStaticLibrary(built);
1503
+ } else {
1504
+ builder.linkDynamicLibrary(built);
1505
+ }
1506
+ } else {
1507
+ builder.linkDynamicLibrary(lib);
1508
+ }
1509
+ }
1510
+ }
1511
+
1512
+ class KeystoreResource {
1513
+ #inner;
1514
+ #client;
1515
+
1516
+ constructor(inner, client) {
1517
+ this.#inner = inner;
1518
+ this.#client = client;
1519
+ }
1520
+
1521
+ async insert(accountId, secretKey) {
1522
+ this.#client.assertNotTerminated();
1523
+ if (this.#inner.keystore) {
1524
+ return await this.#inner.keystore.insert(accountId, secretKey);
1525
+ }
1526
+ return await this.#inner.addAccountSecretKeyToWebStore(
1527
+ accountId,
1528
+ secretKey
1529
+ );
1530
+ }
1531
+
1532
+ async get(pubKeyCommitment) {
1533
+ this.#client.assertNotTerminated();
1534
+ if (this.#inner.keystore) {
1535
+ return await this.#inner.keystore.get(pubKeyCommitment);
1536
+ }
1537
+ return await this.#inner.getAccountAuthByPubKeyCommitment(pubKeyCommitment);
1538
+ }
1539
+
1540
+ async remove(pubKeyCommitment) {
1541
+ this.#client.assertNotTerminated();
1542
+ if (this.#inner.keystore) {
1543
+ return await this.#inner.keystore.remove(pubKeyCommitment);
1544
+ }
1545
+ throw new Error("remove() is not supported on this platform");
1546
+ }
1547
+
1548
+ async getCommitments(accountId) {
1549
+ this.#client.assertNotTerminated();
1550
+ if (this.#inner.keystore) {
1551
+ return await this.#inner.keystore.getCommitments(accountId);
1552
+ }
1553
+ return await this.#inner.getPublicKeyCommitmentsOfAccount(accountId);
1554
+ }
1555
+
1556
+ async getAccountId(pubKeyCommitment) {
1557
+ this.#client.assertNotTerminated();
1558
+ if (this.#inner.keystore) {
1559
+ return await this.#inner.keystore.getAccountId(pubKeyCommitment);
1560
+ }
1561
+ const account =
1562
+ await this.#inner.getAccountByKeyCommitment(pubKeyCommitment);
1563
+ return account ? account.id() : undefined;
1564
+ }
1565
+ }
1566
+
1567
+ /**
1568
+ * MidenClient wraps the existing proxy-wrapped WebClient with a resource-based API.
1569
+ *
1570
+ * Resource classes receive the proxy client and call its methods, handling all type
1571
+ * conversions (string -> AccountId, number -> BigInt, string -> enum).
1572
+ */
1573
+ class MidenClient {
1574
+ // Injected by index.js to resolve circular imports
1575
+ static _WasmWebClient = null;
1576
+ static _MockWasmWebClient = null;
1577
+ static _getWasmOrThrow = null;
1578
+
1579
+ #inner;
1580
+ #getWasm;
1581
+ #terminated = false;
1582
+ #defaultProver = null;
1583
+ #isMock = false;
1584
+
1585
+ constructor(inner, getWasm, defaultProver) {
1586
+ this.#inner = inner;
1587
+ this.#getWasm = getWasm;
1588
+ this.#defaultProver = defaultProver ?? null;
1589
+
1590
+ this.accounts = new AccountsResource(inner, getWasm, this);
1591
+ this.transactions = new TransactionsResource(inner, getWasm, this);
1592
+ this.notes = new NotesResource(inner, getWasm, this);
1593
+ this.tags = new TagsResource(inner, getWasm, this);
1594
+ this.settings = new SettingsResource(inner, getWasm, this);
1595
+ this.compile = new CompilerResource(inner, getWasm, this);
1596
+ this.keystore = new KeystoreResource(inner, this);
1597
+ }
1598
+
1599
+ /**
1600
+ * Escape hatch: runs `fn` with exclusive access to the proxied JS
1601
+ * WebClient that backs this MidenClient.
1602
+ *
1603
+ * The proxy forwards missing properties to the underlying wasm-bindgen
1604
+ * `WebClient`, so `fn` can reach lower-level methods like
1605
+ * `executeTransaction`, `proveTransaction[WithProver]`,
1606
+ * `submitProvenTransaction`, `applyTransaction`,
1607
+ * `newSendTransactionRequest`, `newConsumeTransactionRequest`, etc.
1608
+ *
1609
+ * Intended for advanced consumers that need to split the bundled
1610
+ * execute → prove → submit → apply pipeline across contexts — for example,
1611
+ * a Chrome MV3 extension that runs `executeTransaction` in its service
1612
+ * worker, dispatches the prove step to a `chrome.offscreen` document
1613
+ * (where wasm-bindgen-rayon can spawn a real thread pool), then runs
1614
+ * `submitProvenTransaction` + `applyTransaction` back in the SW.
1615
+ *
1616
+ * The callback runs inside `_serializeWasmCall`, so the WASM RefCell is
1617
+ * held for the duration of `fn`. Concurrent SDK calls (sync, other
1618
+ * transactions, etc.) queue on the same chain and run after `fn`
1619
+ * settles. Without this serialization, raw inner-client access would
1620
+ * race the proxy's chain and trip wasm-bindgen's "recursive use of an
1621
+ * object detected" panic.
1622
+ *
1623
+ * Re-entrancy: while `fn` is running, the underlying client's
1624
+ * `_withInnerLockDepth` counter is bumped so that `_serializeWasmCall`
1625
+ * invocations made BY `fn` (or any proxy-dispatched method it calls)
1626
+ * run inline rather than enqueuing on the chain. Without this, every
1627
+ * `await inner.X(...)` inside `fn` would enqueue behind the outer
1628
+ * `_withInnerWebClient` slot which is itself awaiting `fn` —
1629
+ * a classic re-entrant-lock deadlock. The depth counter restores the
1630
+ * intent of the docstring above: the lock is held for the duration
1631
+ * of `fn`, and inner-client calls "borrow" that already-held lock
1632
+ * instead of trying to re-acquire it.
1633
+ *
1634
+ * SAFETY CONTRACT for re-entrancy: callers MUST hold an external
1635
+ * mutex preventing concurrent access to this same client instance
1636
+ * via other code paths during `fn`. The chain still serializes
1637
+ * against external callers — they queue behind the outer slot — but
1638
+ * if an external task runs during one of `fn`'s awaits and calls
1639
+ * into the SDK, it will see `_withInnerLockDepth > 0` and run
1640
+ * inline, racing wasm-bindgen's borrow check. The wallet pattern
1641
+ * (own outer mutex around `_withInnerWebClient`) satisfies this.
1642
+ *
1643
+ * Stability: marked `@internal`. The shape of the proxied client is
1644
+ * intentionally not part of the documented public API and may change
1645
+ * between SDK versions. If you depend on this method, pin the SDK
1646
+ * version and test the lower-level surface carefully on each upgrade.
1647
+ * If your use case is common enough to warrant a stable public API,
1648
+ * file an issue.
1649
+ *
1650
+ * @internal
1651
+ * @template T
1652
+ * @param {(inner: object) => Promise<T>} fn - Async callback receiving
1653
+ * the proxied JS WebClient. Must not return references that escape
1654
+ * the callback's lifetime (the lock is released on settle).
1655
+ * @returns {Promise<T>} The resolved value of `fn`.
1656
+ */
1657
+ _withInnerWebClient(fn) {
1658
+ this.assertNotTerminated();
1659
+ if (typeof fn !== "function") {
1660
+ throw new TypeError("_withInnerWebClient: fn must be a function");
1661
+ }
1662
+ const inner = this.#inner;
1663
+ return inner._serializeWasmCall(async () => {
1664
+ inner._withInnerLockDepth = (inner._withInnerLockDepth || 0) + 1;
1665
+ try {
1666
+ return await fn(inner);
1667
+ } finally {
1668
+ inner._withInnerLockDepth--;
1669
+ }
1670
+ });
1671
+ }
1672
+
1673
+ /**
1674
+ * Creates and initializes a new MidenClient.
1675
+ *
1676
+ * If no `rpcUrl` is provided, defaults to testnet with full configuration
1677
+ * (RPC, prover, note transport, autoSync).
1678
+ *
1679
+ * @param {ClientOptions} [options] - Client configuration options.
1680
+ * @returns {Promise<MidenClient>} A fully initialized client.
1681
+ */
1682
+ static async create(options) {
1683
+ if (!options?.rpcUrl) {
1684
+ return MidenClient.createTestnet(options);
1685
+ }
1686
+
1687
+ const getWasm = MidenClient._getWasmOrThrow;
1688
+ const WebClientClass = MidenClient._WasmWebClient;
1689
+
1690
+ if (!WebClientClass || !getWasm) {
1691
+ throw new Error(
1692
+ "MidenClient not initialized. Import from the SDK package entry point."
1693
+ );
1694
+ }
1695
+
1696
+ const seed = options?.seed ? await hashSeed(options.seed) : undefined;
1697
+
1698
+ const rpcUrl = resolveRpcUrl(options?.rpcUrl);
1699
+ const noteTransportUrl = resolveNoteTransportUrl(options?.noteTransportUrl);
1700
+
1701
+ // `useWorker: false` opts out of the Web Worker shim that wraps every
1702
+ // WASM call. The shim exists to keep the main thread responsive in
1703
+ // browser/extension contexts, but it serializes the prover via
1704
+ // `TransactionProver.serialize()` — a format that has no encoding for
1705
+ // `newCallbackProver(jsFn)` and silently downgrades it to `"local"`.
1706
+ // Mobile/Tauri/native-prover consumers must pass `useWorker: false`.
1707
+ const useWorker = options?.useWorker;
1708
+ let inner;
1709
+ if (options?.keystore) {
1710
+ inner = await WebClientClass.createClientWithExternalKeystore(
1711
+ rpcUrl,
1712
+ noteTransportUrl,
1713
+ seed,
1714
+ options?.storeName,
1715
+ options.keystore.getKey,
1716
+ options.keystore.insertKey,
1717
+ options.keystore.sign,
1718
+ options?.debugMode,
1719
+ useWorker
1720
+ );
1721
+ } else {
1722
+ inner = await WebClientClass.createClient(
1723
+ rpcUrl,
1724
+ noteTransportUrl,
1725
+ seed,
1726
+ options?.storeName,
1727
+ options?.debugMode,
1728
+ useWorker
1729
+ );
1730
+ }
1731
+
1732
+ let defaultProver = null;
1733
+ if (options?.proverUrl) {
1734
+ const wasm = await getWasm();
1735
+ defaultProver = resolveProver(options.proverUrl, wasm);
1736
+ }
1737
+
1738
+ const client = new MidenClient(inner, getWasm, defaultProver);
1739
+
1740
+ if (options?.autoSync) {
1741
+ await client.sync();
1742
+ }
1743
+
1744
+ return client;
1745
+ }
1746
+
1747
+ /**
1748
+ * Creates a client preconfigured for testnet use.
1749
+ *
1750
+ * Defaults: rpcUrl "testnet", proverUrl "testnet", noteTransportUrl "testnet", autoSync true.
1751
+ * All defaults can be overridden via options.
1752
+ *
1753
+ * @param {ClientOptions} [options] - Options to override defaults.
1754
+ * @returns {Promise<MidenClient>} A fully initialized testnet client.
1755
+ */
1756
+ static async createTestnet(options) {
1757
+ return MidenClient.create({
1758
+ rpcUrl: "testnet",
1759
+ proverUrl: "testnet",
1760
+ noteTransportUrl: "testnet",
1761
+ autoSync: true,
1762
+ ...options,
1763
+ });
1764
+ }
1765
+
1766
+ /**
1767
+ * Creates a client preconfigured for devnet use.
1768
+ *
1769
+ * Defaults: rpcUrl "devnet", proverUrl "devnet", noteTransportUrl "devnet", autoSync true.
1770
+ * All defaults can be overridden via options.
1771
+ *
1772
+ * @param {ClientOptions} [options] - Options to override defaults.
1773
+ * @returns {Promise<MidenClient>} A fully initialized devnet client.
1774
+ */
1775
+ static async createDevnet(options) {
1776
+ return MidenClient.create({
1777
+ rpcUrl: "devnet",
1778
+ proverUrl: "devnet",
1779
+ noteTransportUrl: "devnet",
1780
+ autoSync: true,
1781
+ ...options,
1782
+ });
1783
+ }
1784
+
1785
+ /**
1786
+ * Resolves once the WASM module is initialized and safe to use.
1787
+ *
1788
+ * Idempotent and shared across callers: the underlying loader memoizes the
1789
+ * in-flight promise, so concurrent `ready()` calls await the same
1790
+ * initialization and post-init callers resolve immediately from a cached
1791
+ * module. Safe to call from `MidenProvider`, tutorial helpers, and any
1792
+ * other consumer simultaneously.
1793
+ *
1794
+ * Useful on the `/lazy` entry (e.g. Next.js / Capacitor), where no
1795
+ * top-level await runs at import time. On the default (eager) entry this
1796
+ * is redundant — importing the module already awaits WASM — but calling it
1797
+ * is still harmless.
1798
+ *
1799
+ * @returns {Promise<void>} Resolves when WASM is initialized.
1800
+ */
1801
+ static async ready() {
1802
+ const getWasm = MidenClient._getWasmOrThrow;
1803
+ if (!getWasm) {
1804
+ throw new Error(
1805
+ "MidenClient not initialized. Import from the SDK package entry point."
1806
+ );
1807
+ }
1808
+ await getWasm();
1809
+ }
1810
+
1811
+ /**
1812
+ * Creates a mock client for testing.
1813
+ *
1814
+ * @param {MockOptions} [options] - Mock client options.
1815
+ * @returns {Promise<MidenClient>} A mock client.
1816
+ */
1817
+ static async createMock(options) {
1818
+ const getWasm = MidenClient._getWasmOrThrow;
1819
+ const MockWebClientClass = MidenClient._MockWasmWebClient;
1820
+
1821
+ if (!MockWebClientClass || !getWasm) {
1822
+ throw new Error(
1823
+ "MidenClient not initialized. Import from the SDK package entry point."
1824
+ );
1825
+ }
1826
+
1827
+ const seed = options?.seed ? await hashSeed(options.seed) : undefined;
1828
+
1829
+ const inner = await MockWebClientClass.createClient(
1830
+ options?.serializedMockChain,
1831
+ options?.serializedNoteTransport,
1832
+ seed
1833
+ );
1834
+
1835
+ const client = new MidenClient(inner, getWasm, null);
1836
+ client.#isMock = true;
1837
+ return client;
1838
+ }
1839
+
1840
+ /** Returns the client-level default prover (set from ClientOptions.proverUrl). */
1841
+ get defaultProver() {
1842
+ return this.#defaultProver;
1843
+ }
1844
+
1845
+ /**
1846
+ * Syncs the client: fetches private notes from the Note Transport Layer, then syncs on-chain
1847
+ * state with the Miden node. Fails fast on either.
1848
+ *
1849
+ * @returns {Promise<SyncSummary>} The sync summary.
1850
+ */
1851
+ async sync() {
1852
+ this.assertNotTerminated();
1853
+ return await this.#inner.syncState();
1854
+ }
1855
+
1856
+ /**
1857
+ * Syncs on-chain state only (no NTL fetch).
1858
+ *
1859
+ * @returns {Promise<SyncSummary>}
1860
+ */
1861
+ async syncChain() {
1862
+ this.assertNotTerminated();
1863
+ return await this.#inner.syncChain();
1864
+ }
1865
+
1866
+ /**
1867
+ * Fetches private notes from the Note Transport Layer.
1868
+ *
1869
+ * @returns {Promise<void>}
1870
+ */
1871
+ async syncNoteTransport() {
1872
+ this.assertNotTerminated();
1873
+ return await this.#inner.syncNoteTransport();
1874
+ }
1875
+
1876
+ /**
1877
+ * Returns the current sync height.
1878
+ *
1879
+ * @returns {Promise<number>} The current sync height.
1880
+ */
1881
+ async getSyncHeight() {
1882
+ this.assertNotTerminated();
1883
+ return await this.#inner.getSyncHeight();
1884
+ }
1885
+
1886
+ /**
1887
+ * Resolves once every serialized WASM call that was already on the
1888
+ * internal `_serializeWasmCall` chain when `waitForIdle()` was called
1889
+ * (execute, submit, prove, apply, sync, or account creation) has
1890
+ * settled. Use this from callers that need to perform a non-WASM-side
1891
+ * action — e.g. clearing an in-memory auth key on wallet lock — after
1892
+ * the kernel finishes, so its auth callback doesn't race with the key
1893
+ * being cleared.
1894
+ *
1895
+ * Does NOT wait for calls enqueued after `waitForIdle()` returns —
1896
+ * intentional, so a caller can drain and proceed without being blocked
1897
+ * indefinitely by concurrent workload.
1898
+ *
1899
+ * Caveat for `syncState`: `syncStateWithTimeout` awaits the sync lock
1900
+ * (`acquireSyncLock`, which uses Web Locks) BEFORE putting its WASM
1901
+ * call onto the chain, so a `syncState` that is queued on the sync
1902
+ * lock — but has not yet begun its WASM phase — is not visible to
1903
+ * `waitForIdle` and will not be awaited. Other methods (`newWallet`,
1904
+ * `executeTransaction`, etc.) route through the chain synchronously
1905
+ * on call and are always observed.
1906
+ *
1907
+ * Safe to call at any time; returns immediately if nothing was in
1908
+ * flight.
1909
+ *
1910
+ * @returns {Promise<void>}
1911
+ */
1912
+ async waitForIdle() {
1913
+ this.assertNotTerminated();
1914
+ await this.#inner.waitForIdle();
1915
+ }
1916
+
1917
+ /**
1918
+ * Returns the raw JS value that the most recent sign-callback invocation
1919
+ * threw, or `null` if the last sign call succeeded (or no call has
1920
+ * happened yet).
1921
+ *
1922
+ * Useful for recovering structured metadata (e.g. a `reason: 'locked'`
1923
+ * property) that the kernel-level `auth::request` diagnostic would
1924
+ * otherwise erase. Call immediately after catching a failed
1925
+ * `transactions.submit` / `transactions.send` / `transactions.consume`.
1926
+ *
1927
+ * Meaningful only with `useWorker: false`: under the worker shim the
1928
+ * sign callback fires against the worker's WASM keystore, while this
1929
+ * accessor reads the main-thread instance — which never signed — so it
1930
+ * returns `null`. Consumers that need this signal (e.g. external
1931
+ * keystores with lock-aware sign callbacks) already require
1932
+ * `useWorker: false` for the callback to be reachable at all.
1933
+ *
1934
+ * @returns {any} The raw thrown value, or `null`.
1935
+ */
1936
+ lastAuthError() {
1937
+ this.assertNotTerminated();
1938
+ return this.#inner.lastAuthError();
1939
+ }
1940
+
1941
+ /**
1942
+ * Terminates the underlying Web Worker. After this, all method calls will throw.
1943
+ */
1944
+ terminate() {
1945
+ this.#terminated = true;
1946
+ this.#inner.terminate?.();
1947
+ }
1948
+
1949
+ [Symbol.dispose]() {
1950
+ this.terminate();
1951
+ }
1952
+
1953
+ async [Symbol.asyncDispose]() {
1954
+ this.terminate();
1955
+ }
1956
+
1957
+ /**
1958
+ * Returns the identifier of the underlying store (e.g. IndexedDB database name, file path).
1959
+ *
1960
+ * @returns {string} The store identifier.
1961
+ */
1962
+ async storeIdentifier() {
1963
+ this.assertNotTerminated();
1964
+ return await this.#inner.storeIdentifier();
1965
+ }
1966
+
1967
+ // ── Mock-only methods ──
1968
+
1969
+ /** Advances the mock chain by one block. Only available on mock clients. */
1970
+ proveBlock() {
1971
+ this.assertNotTerminated();
1972
+ this.#assertMock("proveBlock");
1973
+ return this.#inner.proveBlock();
1974
+ }
1975
+
1976
+ /** Returns true if this client uses a mock chain. */
1977
+ usesMockChain() {
1978
+ return this.#isMock;
1979
+ }
1980
+
1981
+ /** Serializes the mock chain state for snapshot/restore in tests. */
1982
+ serializeMockChain() {
1983
+ this.assertNotTerminated();
1984
+ this.#assertMock("serializeMockChain");
1985
+ return this.#inner.serializeMockChain();
1986
+ }
1987
+
1988
+ /** Serializes the mock note transport node state. */
1989
+ serializeMockNoteTransportNode() {
1990
+ this.assertNotTerminated();
1991
+ this.#assertMock("serializeMockNoteTransportNode");
1992
+ return this.#inner.serializeMockNoteTransportNode();
1993
+ }
1994
+
1995
+ // ── Internal ──
1996
+
1997
+ /** @internal Throws if the client has been terminated. */
1998
+ assertNotTerminated() {
1999
+ if (this.#terminated) {
2000
+ throw new Error("Client terminated");
2001
+ }
2002
+ }
2003
+
2004
+ #assertMock(method) {
2005
+ if (!this.#isMock) {
2006
+ throw new Error(`${method}() is only available on mock clients`);
2007
+ }
2008
+ }
2009
+ }
2010
+
2011
+ const RPC_URLS = {
2012
+ testnet: "https://rpc.testnet.miden.io",
2013
+ devnet: "https://rpc.devnet.miden.io",
2014
+ localhost: "http://localhost:57291",
2015
+ local: "http://localhost:57291",
2016
+ };
2017
+
2018
+ /**
2019
+ * Resolves an rpcUrl shorthand or raw URL into a concrete endpoint string.
2020
+ *
2021
+ * @param {string | undefined} rpcUrl - "testnet", "devnet", "localhost", "local", or a raw URL.
2022
+ * @returns {string | undefined} A fully qualified URL, or undefined to use the SDK default.
2023
+ */
2024
+ function resolveRpcUrl(rpcUrl) {
2025
+ if (!rpcUrl) return undefined;
2026
+ return RPC_URLS[rpcUrl.trim().toLowerCase()] ?? rpcUrl;
2027
+ }
2028
+
2029
+ const PROVER_URLS = {
2030
+ devnet: "https://tx-prover.devnet.miden.io",
2031
+ testnet: "https://tx-prover.testnet.miden.io",
2032
+ };
2033
+
2034
+ const NOTE_TRANSPORT_URLS = {
2035
+ testnet: "https://transport.miden.io",
2036
+ devnet: "https://transport.devnet.miden.io",
2037
+ };
2038
+
2039
+ /**
2040
+ * Resolves a noteTransportUrl shorthand or raw URL into a concrete endpoint string.
2041
+ *
2042
+ * @param {string | undefined} noteTransportUrl - "testnet", "devnet", or a raw URL.
2043
+ * @returns {string | undefined} A fully qualified URL, or undefined if omitted.
2044
+ */
2045
+ function resolveNoteTransportUrl(noteTransportUrl) {
2046
+ if (!noteTransportUrl) return undefined;
2047
+ return (
2048
+ NOTE_TRANSPORT_URLS[noteTransportUrl.trim().toLowerCase()] ??
2049
+ noteTransportUrl
2050
+ );
2051
+ }
2052
+
2053
+ /**
2054
+ * Resolves a proverUrl shorthand or raw URL into a TransactionProver.
2055
+ *
2056
+ * @param {string} proverUrl - "local", "devnet", "testnet", or a raw URL.
2057
+ * @param {object} wasm - Loaded WASM module.
2058
+ * @returns {object} A TransactionProver instance.
2059
+ */
2060
+ function resolveProver(proverUrl, wasm) {
2061
+ const normalized = proverUrl.trim().toLowerCase();
2062
+ if (normalized === "local") {
2063
+ return wasm.TransactionProver.newLocalProver();
2064
+ }
2065
+ const remoteUrl = PROVER_URLS[normalized] ?? proverUrl;
2066
+ return wasm.TransactionProver.newRemoteProver(remoteUrl, undefined);
2067
+ }
2068
+
2069
+ // Module-level WASM reference, set by index.js after initialization
2070
+ let _wasm = null;
2071
+ let _WebClient = null;
2072
+
2073
+ function _setWasm(wasm) {
2074
+ _wasm = wasm;
2075
+ }
2076
+
2077
+ function _setWebClient(WebClientClass) {
2078
+ _WebClient = WebClientClass;
2079
+ }
2080
+
2081
+ function getWasm() {
2082
+ if (!_wasm) {
2083
+ throw new Error(
2084
+ "WASM not initialized. Ensure the SDK is loaded before calling standalone utilities."
2085
+ );
2086
+ }
2087
+ return _wasm;
2088
+ }
2089
+
2090
+ /**
2091
+ * Creates a P2ID (Pay-to-ID) note.
2092
+ *
2093
+ * @param {NoteOptions} opts - Note creation options.
2094
+ * @returns {Note} The created note.
2095
+ */
2096
+ function createP2IDNote(opts) {
2097
+ const wasm = getWasm();
2098
+ const sender = resolveAccountRef(opts.from, wasm);
2099
+ const target = resolveAccountRef(opts.to, wasm);
2100
+ const noteAssets = buildNoteAssets(opts.assets, wasm);
2101
+ const noteType = resolveNoteType(opts.type, wasm);
2102
+ const attachment = opts.attachment
2103
+ ? new wasm.NoteAttachment(opts.attachment)
2104
+ : new wasm.NoteAttachment([]);
2105
+
2106
+ return wasm.Note.createP2IDNote(
2107
+ sender,
2108
+ target,
2109
+ noteAssets,
2110
+ noteType,
2111
+ attachment
2112
+ );
2113
+ }
2114
+
2115
+ /**
2116
+ * Creates a P2IDE (Pay-to-ID with Expiration) note.
2117
+ *
2118
+ * @param {P2IDEOptions} opts - Note creation options with timelock/reclaim.
2119
+ * @returns {Note} The created note.
2120
+ */
2121
+ function createP2IDENote(opts) {
2122
+ const wasm = getWasm();
2123
+ const sender = resolveAccountRef(opts.from, wasm);
2124
+ const target = resolveAccountRef(opts.to, wasm);
2125
+ const noteAssets = buildNoteAssets(opts.assets, wasm);
2126
+ const noteType = resolveNoteType(opts.type, wasm);
2127
+ const attachment = opts.attachment
2128
+ ? new wasm.NoteAttachment(opts.attachment)
2129
+ : new wasm.NoteAttachment([]);
2130
+
2131
+ return wasm.Note.createP2IDENote(
2132
+ sender,
2133
+ target,
2134
+ noteAssets,
2135
+ opts.reclaimAfter,
2136
+ opts.timelockUntil,
2137
+ noteType,
2138
+ attachment
2139
+ );
2140
+ }
2141
+
2142
+ /**
2143
+ * Builds a swap tag for note matching.
2144
+ *
2145
+ * @param {BuildSwapTagOptions} opts - Swap tag options.
2146
+ * @returns {NoteTag} The computed swap tag.
2147
+ */
2148
+ function buildSwapTag(opts) {
2149
+ const wasm = getWasm();
2150
+ if (!_WebClient || typeof _WebClient.buildSwapTag !== "function") {
2151
+ throw new Error(
2152
+ "WebClient.buildSwapTag is not available. Ensure the SDK is fully loaded."
2153
+ );
2154
+ }
2155
+ const noteType = resolveNoteType(opts.type, wasm);
2156
+ const offeredFaucetId = resolveAccountRef(opts.offer.token, wasm);
2157
+ const requestedFaucetId = resolveAccountRef(opts.request.token, wasm);
2158
+
2159
+ return _WebClient.buildSwapTag(
2160
+ noteType,
2161
+ offeredFaucetId,
2162
+ BigInt(opts.offer.amount),
2163
+ requestedFaucetId,
2164
+ BigInt(opts.request.amount)
2165
+ );
2166
+ }
2167
+
2168
+ function buildNoteAssets(assets, wasm) {
2169
+ const assetArray = Array.isArray(assets) ? assets : [assets];
2170
+ const fungibleAssets = assetArray.map((asset) => {
2171
+ const faucetId = resolveAccountRef(asset.token, wasm);
2172
+ return new wasm.FungibleAsset(faucetId, BigInt(asset.amount));
2173
+ });
2174
+ return new wasm.NoteAssets(fungibleAssets);
2175
+ }
2176
+
2177
+ /**
2178
+ * StorageView wraps the raw WASM AccountStorage to provide a developer-friendly
2179
+ * (and AI-agent-friendly) API.
2180
+ *
2181
+ * Key behavior: `getItem()` returns a `StorageResult` that works intuitively for
2182
+ * both Value and StorageMap slots. The result has `.toBigInt()`, `.toHex()`, and
2183
+ * `.toString()` methods that do the right thing automatically. For StorageMap slots,
2184
+ * `.entries` provides access to all map entries.
2185
+ *
2186
+ * Numeric ergonomics: `StorageResult` is usable directly in template strings,
2187
+ * JSX, and arithmetic via `toString()` (lossless, BigInt-backed) and `valueOf()`
2188
+ * (returns a JS number for values that fit, throws on overflow — never silently
2189
+ * corrupts). For exact u64 access use `.toBigInt()`.
2190
+ *
2191
+ * The raw WASM AccountStorage is still accessible via `.raw` for advanced use cases
2192
+ * that need the original behavior (e.g., comparing map commitment roots).
2193
+ */
2194
+ /** @param {string} hex @param {typeof Word} WordClass @returns {Word | undefined} */
2195
+ function hexToWord(hex, WordClass) {
2196
+ if (!hex || !WordClass) return undefined;
2197
+ try {
2198
+ return WordClass.fromHex(hex);
2199
+ } catch {
2200
+ return undefined;
2201
+ }
2202
+ }
2203
+
2204
+ class StorageView {
2205
+ #storage;
2206
+ #WordClass;
2207
+
2208
+ /**
2209
+ * @param {AccountStorage} wasmStorage
2210
+ * @param {typeof Word} WordClass
2211
+ */
2212
+ constructor(wasmStorage, WordClass) {
2213
+ this.#storage = wasmStorage;
2214
+ this.#WordClass = WordClass;
2215
+ }
2216
+
2217
+ /**
2218
+ * The raw WASM AccountStorage, for cases where you need the original
2219
+ * primitive behavior (e.g., reading map commitment roots via raw.getItem()).
2220
+ */
2221
+ get raw() {
2222
+ return this.#storage;
2223
+ }
2224
+
2225
+ /**
2226
+ * Returns the commitment to the full account storage.
2227
+ */
2228
+ commitment() {
2229
+ return this.#storage.commitment();
2230
+ }
2231
+
2232
+ /**
2233
+ * Returns the names of all storage slots on this account.
2234
+ * @returns {string[]}
2235
+ */
2236
+ getSlotNames() {
2237
+ return this.#storage.getSlotNames();
2238
+ }
2239
+
2240
+ /**
2241
+ * Returns a StorageResult for the given slot.
2242
+ *
2243
+ * The result has convenience methods that work for both Value and StorageMap slots:
2244
+ * - `.toBigInt()` — first felt as BigInt (full u64 precision)
2245
+ * - `.toHex()` — first felt's Word as hex string
2246
+ * - `.toString()` — renders as the BigInt value (works in JSX: {result})
2247
+ * - `.isMap` — true if this is a StorageMap slot
2248
+ * - `.entries` — all map entries (undefined for Value slots)
2249
+ * - `.word` — the underlying Word value
2250
+ *
2251
+ * The result is also usable directly in arithmetic (`+result`, `result * 2`)
2252
+ * via `valueOf()`, which returns a JS number for values that fit and throws
2253
+ * `RangeError` for values exceeding `Number.MAX_SAFE_INTEGER` — use `.toBigInt()`
2254
+ * for exact access to large u64 values.
2255
+ *
2256
+ * For explicit key-based map reads, use `getMapItem(slotName, key)`.
2257
+ * For the raw commitment hash, use `raw.getItem(slotName)`.
2258
+ *
2259
+ * @param {string} slotName
2260
+ * @returns {StorageResult | undefined}
2261
+ */
2262
+ getItem(slotName) {
2263
+ // Type detection + value retrieval in one pass.
2264
+ // We call getMapEntries to detect maps, but defer parsing the entries
2265
+ // until .entries is actually accessed (lazy). Only the first entry's
2266
+ // Word is parsed eagerly for the convenience methods (toBigInt, etc.).
2267
+ const rawEntries = this.#storage.getMapEntries(slotName);
2268
+ if (rawEntries !== undefined && rawEntries !== null) {
2269
+ // StorageMap — parse only the first entry eagerly
2270
+ const firstWord =
2271
+ rawEntries.length > 0
2272
+ ? hexToWord(rawEntries[0].value, this.#WordClass)
2273
+ : undefined;
2274
+ return new StorageResult(firstWord, true, rawEntries, this.#WordClass);
2275
+ }
2276
+
2277
+ // Value slot — use raw getItem
2278
+ const word = this.#storage.getItem(slotName);
2279
+ if (!word) return undefined;
2280
+ return new StorageResult(word, false, undefined, this.#WordClass);
2281
+ }
2282
+
2283
+ /**
2284
+ * Returns the value for a key in a StorageMap slot.
2285
+ * Delegates directly to the raw WASM method.
2286
+ *
2287
+ * @param {string} slotName
2288
+ * @param {Word} key
2289
+ * @returns {Word | undefined}
2290
+ */
2291
+ getMapItem(slotName, key) {
2292
+ return this.#storage.getMapItem(slotName, key);
2293
+ }
2294
+
2295
+ /**
2296
+ * Get all key-value pairs from a StorageMap slot.
2297
+ * Returns undefined if the slot isn't a map, or an empty array if the map is empty.
2298
+ */
2299
+ getMapEntries(slotName) {
2300
+ return this.#storage.getMapEntries(slotName);
2301
+ }
2302
+
2303
+ /**
2304
+ * Returns the commitment root of a storage slot as a Word.
2305
+ *
2306
+ * For Value slots, this is the stored Word itself.
2307
+ * For StorageMap slots, this is the Merkle root hash of the map — useful for:
2308
+ * - Verifying state hasn't changed between transactions
2309
+ * - Merkle inclusion proofs against the account state
2310
+ * - Comparing map state across accounts or sync cycles
2311
+ *
2312
+ * This is the raw protocol-level value. For reading stored data, use `getItem()`.
2313
+ *
2314
+ * @param {string} slotName
2315
+ * @returns {Word | undefined}
2316
+ */
2317
+ getCommitment(slotName) {
2318
+ return this.#storage.getItem(slotName);
2319
+ }
2320
+ }
2321
+
2322
+ /**
2323
+ * Result of reading a storage slot. Works for both Value and StorageMap slots.
2324
+ *
2325
+ * Provides a unified interface so code like `storage.getItem(name).toBigInt()`
2326
+ * works regardless of the underlying slot type.
2327
+ *
2328
+ * For StorageMap slots, the convenience methods (toHex, toBigInt) operate on
2329
+ * the first entry's value. The full map data is available via `.entries`.
2330
+ * Note: Miden storage maps are Merkle-based, so "first" is determined by key hash
2331
+ * order — deterministic for a given map state, but not meaningful as an ordering.
2332
+ */
2333
+ class StorageResult {
2334
+ #word;
2335
+ #isMap;
2336
+ #rawEntries; // Raw JsStorageMapEntry[] from WASM — parsed lazily
2337
+ #parsedEntries; // Parsed entries with Word objects — created on first .entries access
2338
+ #WordClass;
2339
+
2340
+ /**
2341
+ * @param {Word | undefined} word — the primary Word value (first entry for maps)
2342
+ * @param {boolean} isMap — whether this came from a StorageMap slot
2343
+ * @param {Array | undefined} rawEntries — raw WASM entries (parsed lazily on .entries access)
2344
+ * @param {typeof Word} WordClass — Word constructor for hex parsing
2345
+ */
2346
+ constructor(word, isMap, rawEntries, WordClass) {
2347
+ this.#word = word;
2348
+ this.#isMap = isMap;
2349
+ this.#rawEntries = rawEntries;
2350
+ this.#WordClass = WordClass;
2351
+ }
2352
+
2353
+ /** True if this slot is a StorageMap. */
2354
+ get isMap() {
2355
+ return this.#isMap;
2356
+ }
2357
+
2358
+ /**
2359
+ * All entries from a StorageMap slot (lazily parsed on first access).
2360
+ * Each entry has { key: string (hex), value: string (hex), word: Word | undefined }.
2361
+ * Returns undefined for Value slots.
2362
+ */
2363
+ get entries() {
2364
+ if (!this.#isMap) return undefined;
2365
+ if (this.#parsedEntries) return this.#parsedEntries;
2366
+ if (!this.#rawEntries) return [];
2367
+
2368
+ // Parse entries lazily — only when the user actually accesses .entries
2369
+ this.#parsedEntries = this.#rawEntries.map((e) => ({
2370
+ key: e.key,
2371
+ value: e.value,
2372
+ word: hexToWord(e.value, this.#WordClass),
2373
+ }));
2374
+ this.#rawEntries = undefined; // Free raw entries
2375
+ return this.#parsedEntries;
2376
+ }
2377
+
2378
+ /**
2379
+ * The underlying Word value.
2380
+ * For Value slots: the stored Word.
2381
+ * For StorageMap slots: the first entry's value as a Word (or undefined if empty).
2382
+ */
2383
+ get word() {
2384
+ return this.#word;
2385
+ }
2386
+
2387
+ /**
2388
+ * Returns all four Felts of the stored Word as an array.
2389
+ * Pass-through to Word.toFelts() — ensures code that expects a Word-like
2390
+ * object (e.g., `result.toFelts()[0].asInt()`) works on StorageResult.
2391
+ * @returns {Felt[]}
2392
+ */
2393
+ toFelts() {
2394
+ if (!this.#word) return [];
2395
+ return this.#word.toFelts();
2396
+ }
2397
+
2398
+ /**
2399
+ * The first Felt of the stored Word.
2400
+ * Returns the WASM Felt object — use .asInt() to get its BigInt value.
2401
+ * @returns {Felt | undefined}
2402
+ */
2403
+ felt() {
2404
+ if (!this.#word) return undefined;
2405
+ const felts = this.#word.toFelts();
2406
+ return felts?.[0];
2407
+ }
2408
+
2409
+ /**
2410
+ * First felt as a BigInt. Preserves full u64 precision.
2411
+ * @returns {bigint}
2412
+ */
2413
+ toBigInt() {
2414
+ if (!this.#word) return 0n;
2415
+ return wordToBigInt(this.#word);
2416
+ }
2417
+
2418
+ /**
2419
+ * The Word's hex representation.
2420
+ * For Value slots: the stored Word hex.
2421
+ * For StorageMap slots: the first entry's value Word hex.
2422
+ * @returns {string}
2423
+ */
2424
+ toHex() {
2425
+ if (!this.#word) return "0x" + "0".repeat(64);
2426
+ return this.#word.toHex();
2427
+ }
2428
+
2429
+ /**
2430
+ * Renders as the BigInt value (lossless). Makes `{storageResult}` work in JSX
2431
+ * and template literals: `` `value: ${result}` ``.
2432
+ * @returns {string}
2433
+ */
2434
+ toString() {
2435
+ return this.toBigInt().toString();
2436
+ }
2437
+
2438
+ /**
2439
+ * JSON serialization — returns the value as a string to avoid
2440
+ * precision loss for large u64 felt values.
2441
+ */
2442
+ toJSON() {
2443
+ return this.toBigInt().toString();
2444
+ }
2445
+
2446
+ /**
2447
+ * Allows `+result`, `result * 2`, etc. to work as expected.
2448
+ *
2449
+ * Returns a JS number for values that fit in `Number.MAX_SAFE_INTEGER`
2450
+ * (2^53 - 1). For larger u64 values, throws `RangeError` rather than
2451
+ * silently losing precision — use `.toBigInt()` to access the exact value.
2452
+ *
2453
+ * @returns {number}
2454
+ * @throws {RangeError} if the underlying felt exceeds Number.MAX_SAFE_INTEGER
2455
+ */
2456
+ valueOf() {
2457
+ const big = this.toBigInt();
2458
+ if (big > BigInt(Number.MAX_SAFE_INTEGER)) {
2459
+ throw new RangeError(
2460
+ `StorageResult value ${big} exceeds Number.MAX_SAFE_INTEGER ` +
2461
+ `(${Number.MAX_SAFE_INTEGER}) — use .toBigInt() to read the exact value.`
2462
+ );
2463
+ }
2464
+ return Number(big);
2465
+ }
2466
+ }
2467
+
2468
+ /**
2469
+ * Convert a Word's first felt to a BigInt.
2470
+ * Uses BigInt to preserve full u64 precision (felts are u64-backed).
2471
+ * Handles the little-endian byte order of felt serialization.
2472
+ *
2473
+ * @param {Word} word
2474
+ * @returns {bigint}
2475
+ */
2476
+ function wordToBigInt(word) {
2477
+ try {
2478
+ const hex = word.toHex();
2479
+ // Word.toHex() returns "0x" + 64 hex chars (4 felts × 16 hex chars each).
2480
+ // Each felt is serialized as 8 little-endian bytes, so we take the first 16
2481
+ // hex chars (first felt) and reverse the byte pairs to get the integer value.
2482
+ const feltHex = hex.slice(2, 18);
2483
+ const bytes = feltHex.match(/../g);
2484
+ if (!bytes) return 0n;
2485
+ return BigInt("0x" + bytes.reverse().join(""));
2486
+ } catch {
2487
+ return 0n;
2488
+ }
2489
+ }
2490
+
2491
+ /**
2492
+ * Install the StorageView wrapper on Account.prototype.storage().
2493
+ * After this, `account.storage()` returns a StorageView instead of raw AccountStorage.
2494
+ *
2495
+ * @param {object} wasmModule — the loaded WASM module containing Account, Word, etc.
2496
+ */
2497
+ function installStorageView(wasmModule) {
2498
+ const AccountProto = wasmModule.Account?.prototype;
2499
+ if (!AccountProto || !AccountProto.storage) return;
2500
+
2501
+ const originalStorage = AccountProto.storage;
2502
+ const WordClass = wasmModule.Word;
2503
+
2504
+ AccountProto.storage = function () {
2505
+ const raw = originalStorage.call(this);
2506
+ return new StorageView(raw, WordClass);
2507
+ };
2508
+ }
2509
+
2510
+ const AccountType = Object.freeze({
2511
+ // WASM-compatible numeric values — usable with AccountBuilder directly
2512
+ FungibleFaucet: 0,
2513
+ NonFungibleFaucet: 1,
2514
+ RegularAccountImmutableCode: 2,
2515
+ RegularAccountUpdatableCode: 3,
2516
+ // SDK-friendly aliases (same numeric values as their WASM equivalents)
2517
+ MutableWallet: 3,
2518
+ ImmutableWallet: 2,
2519
+ ImmutableContract: 2,
2520
+ MutableContract: 3,
2521
+ });
2522
+
2523
+ const AuthScheme = Object.freeze({
2524
+ Falcon: "falcon",
2525
+ ECDSA: "ecdsa",
2526
+ });
2527
+
2528
+ const NoteVisibility = Object.freeze({
2529
+ Public: "public",
2530
+ Private: "private",
2531
+ });
2532
+
2533
+ const StorageMode = Object.freeze({
2534
+ Public: "public",
2535
+ Private: "private",
2536
+ });
2537
+
2538
+ const Linking = Object.freeze({
2539
+ Dynamic: "dynamic",
2540
+ Static: "static",
2541
+ });
2542
+
2543
+ const MOCK_STORE_NAME = "mock_client_db";
2544
+
2545
+ const buildTypedArraysExport = (exportObject) => {
2546
+ return Object.entries(exportObject).reduce(
2547
+ (exports$1, [exportName, _export]) => {
2548
+ if (exportName.endsWith("Array")) {
2549
+ exports$1[exportName] = _export;
2550
+ }
2551
+ return exports$1;
2552
+ },
2553
+ {}
2554
+ );
2555
+ };
2556
+
2557
+ const deserializeError = (errorLike) => {
2558
+ if (!errorLike) {
2559
+ return new Error("Unknown error received from worker");
2560
+ }
2561
+ const { name, message, stack, cause, ...rest } = errorLike;
2562
+ const reconstructedError = new Error(message ?? "Unknown worker error");
2563
+ reconstructedError.name = name ?? reconstructedError.name;
2564
+ if (stack) {
2565
+ reconstructedError.stack = stack;
2566
+ }
2567
+ if (cause) {
2568
+ reconstructedError.cause = deserializeError(cause);
2569
+ }
2570
+ Object.entries(rest).forEach(([key, value]) => {
2571
+ if (value !== undefined) {
2572
+ reconstructedError[key] = value;
2573
+ }
2574
+ });
2575
+ return reconstructedError;
2576
+ };
2577
+
2578
+ const MidenArrays = {};
2579
+
2580
+ let wasmModule = null;
2581
+ let wasmLoadPromise = null;
2582
+ let webClientStaticsCopied = false;
2583
+
2584
+ const ensureWasm = async () => {
2585
+ if (wasmModule) {
2586
+ return wasmModule;
2587
+ }
2588
+ if (!wasmLoadPromise) {
2589
+ wasmLoadPromise = loadWasm().then((module) => {
2590
+ wasmModule = module;
2591
+ if (module) {
2592
+ Object.assign(MidenArrays, buildTypedArraysExport(module));
2593
+ if (!webClientStaticsCopied && module.WebClient) {
2594
+ copyWebClientStatics(module.WebClient);
2595
+ webClientStaticsCopied = true;
2596
+ }
2597
+ // Set WASM module for standalone utilities
2598
+ _setWasm(module);
2599
+ // Install StorageView: account.storage() now returns a developer-friendly
2600
+ // wrapper that makes getItem() work correctly for StorageMap slots.
2601
+ installStorageView(module);
2602
+ }
2603
+ return module;
2604
+ });
2605
+ }
2606
+ return wasmLoadPromise;
2607
+ };
2608
+
2609
+ const getWasmOrThrow = async () => {
2610
+ const module = await ensureWasm();
2611
+ if (!module) {
2612
+ throw new Error(
2613
+ "Miden WASM bindings are unavailable in this environment (SSR is disabled)."
2614
+ );
2615
+ }
2616
+ return module;
2617
+ };
2618
+ /**
2619
+ * WebClient is a wrapper around the underlying WASM WebClient object.
2620
+ *
2621
+ * This wrapper serves several purposes:
2622
+ *
2623
+ * 1. It creates a dedicated web worker to offload computationally heavy tasks
2624
+ * (such as creating accounts, executing transactions, submitting transactions, etc.)
2625
+ * from the main thread, helping to prevent UI freezes in the browser.
2626
+ *
2627
+ * 2. It defines methods that mirror the API of the underlying WASM WebClient,
2628
+ * with the intention of executing these functions via the web worker. This allows us
2629
+ * to maintain the same API and parameters while benefiting from asynchronous, worker-based computation.
2630
+ *
2631
+ * 3. It employs a Proxy to forward any calls not designated for web worker computation
2632
+ * directly to the underlying WASM WebClient instance.
2633
+ *
2634
+ * Additionally, the wrapper provides a static createClient function. This static method
2635
+ * instantiates the WebClient object and ensures that the necessary createClient calls are
2636
+ * performed both in the main thread and within the worker thread. This dual initialization
2637
+ * correctly passes user parameters (RPC URL and seed) to both the main-thread
2638
+ * WASM WebClient and the worker-side instance.
2639
+ *
2640
+ * Because of this implementation, the only breaking change for end users is in the way the
2641
+ * web client is instantiated. Users should now use the WebClient.createClient static call.
2642
+ */
2643
+ /**
2644
+ * Create a Proxy that forwards missing properties to the underlying WASM
2645
+ * WebClient.
2646
+ */
2647
+ function createClientProxy(instance) {
2648
+ return new Proxy(instance, {
2649
+ get(target, prop, receiver) {
2650
+ if (prop in target) {
2651
+ return Reflect.get(target, prop, receiver);
2652
+ }
2653
+ if (target.wasmWebClient && prop in target.wasmWebClient) {
2654
+ const value = target.wasmWebClient[prop];
2655
+ if (typeof value === "function") {
2656
+ return value.bind(target.wasmWebClient);
2657
+ }
2658
+ return value;
2659
+ }
2660
+ return undefined;
2661
+ },
2662
+ });
2663
+ }
2664
+
2665
+ class WebClient {
2666
+ /**
2667
+ * Controls which worker variant is spawned when a WebClient is constructed.
2668
+ *
2669
+ * - `"auto"` (default): pick `classic` on Safari/WKWebView (where module
2670
+ * workers have a very slow cold start), `module` everywhere else.
2671
+ * - `"module"`: always use the `.mjs` ES-module worker. Required for webpack
2672
+ * 5 / Next.js consumers so the asset tracer can see the WASM URL.
2673
+ * - `"classic"`: always use the `.js` classic-script worker. Required on
2674
+ * Safari/WKWebView. Set this if your consumer bundler (or your host app)
2675
+ * does not support module workers.
2676
+ *
2677
+ * Set before the first `WebClient.createClient(...)` call.
2678
+ */
2679
+ static workerMode = "auto";
2680
+
2681
+ /**
2682
+ * Decide between the module and classic worker variants based on
2683
+ * `WebClient.workerMode` and (when `auto`) the current user agent.
2684
+ * @returns {boolean} true when the classic script should be used.
2685
+ * @private
2686
+ */
2687
+ static _shouldUseClassicWorker() {
2688
+ const mode = WebClient.workerMode;
2689
+ if (mode === "module") return false;
2690
+ if (mode === "classic") return true;
2691
+ // auto: classic on Safari/WKWebView, module everywhere else.
2692
+ const ua =
2693
+ typeof navigator !== "undefined" && navigator.userAgent
2694
+ ? navigator.userAgent
2695
+ : "";
2696
+ // Chromium-based browsers (Chrome, Edge, Brave, Opera, Chromium-based
2697
+ // Android WebView) handle module workers fine.
2698
+ if (/Chrome\/|Chromium\//.test(ua)) return false;
2699
+ // Safari (desktop + iOS) and WKWebView-without-Chrome (e.g. Capacitor host)
2700
+ // both have AppleWebKit but no Chrome/Chromium in the UA. Prefer classic.
2701
+ if (/AppleWebKit/.test(ua)) return true;
2702
+ // Firefox, jsdom, node without navigator, etc. — module worker is fine.
2703
+ return false;
2704
+ }
2705
+
2706
+ /**
2707
+ * Create a WebClient wrapper.
2708
+ *
2709
+ * @param {string | undefined} rpcUrl - RPC endpoint URL used by the client.
2710
+ * @param {Uint8Array | undefined} seed - Optional seed for account initialization.
2711
+ * @param {string | undefined} storeName - Optional name for the store to be used by the client.
2712
+ * @param {(pubKey: Uint8Array) => Promise<Uint8Array | null | undefined> | Uint8Array | null | undefined} [getKeyCb]
2713
+ * - Callback to retrieve the secret key bytes for a given public key. The `pubKey`
2714
+ * parameter is the serialized public key (from `PublicKey.serialize()`). Return the
2715
+ * corresponding secret key as a `Uint8Array`, or `null`/`undefined` if not found. The
2716
+ * return value may be provided synchronously or via a `Promise`.
2717
+ * @param {(pubKey: Uint8Array, AuthSecretKey: Uint8Array) => Promise<void> | void} [insertKeyCb]
2718
+ * - Callback to persist a secret key. `pubKey` is the serialized public key, and
2719
+ * `authSecretKey` is the serialized secret key (from `AuthSecretKey.serialize()`). May return
2720
+ * `void` or a `Promise<void>`.
2721
+ * @param {(pubKey: Uint8Array, signingInputs: Uint8Array) => Promise<Uint8Array> | Uint8Array} [signCb]
2722
+ * - Callback to produce serialized signature bytes for the provided inputs. `pubKey` is the
2723
+ * serialized public key, and `signingInputs` is a `Uint8Array` produced by
2724
+ * `SigningInputs.serialize()`. Must return a `Uint8Array` containing the serialized
2725
+ * signature, either directly or wrapped in a `Promise`.
2726
+ * @param {string | undefined} [logLevel] - Optional log verbosity level
2727
+ * ("error", "warn", "info", "debug", "trace", "off", or "none").
2728
+ * When set, Rust tracing output is routed to the browser console.
2729
+ * @param {boolean} [useWorker=true] - When `false`, skip the Web Worker shim
2730
+ * and call the wasm-bindgen `WebClient` directly on the current thread.
2731
+ * The worker exists to keep the main thread responsive during WASM work
2732
+ * in browser/extension contexts, but it serializes the prover argument
2733
+ * via `TransactionProver.serialize()` — a format that has no encoding
2734
+ * for `newCallbackProver(jsFn)` and silently downgrades it to `"local"`.
2735
+ * Consumers that hand a `CallbackProver` (e.g. native iOS/Android plug-in
2736
+ * provers in Capacitor apps, or any other JS-side prover bridge) need
2737
+ * `useWorker: false` so the prover handle reaches the WASM binding intact.
2738
+ */
2739
+ constructor(
2740
+ rpcUrl,
2741
+ noteTransportUrl,
2742
+ seed,
2743
+ storeName,
2744
+ getKeyCb,
2745
+ insertKeyCb,
2746
+ signCb,
2747
+ logLevel,
2748
+ useWorker = true
2749
+ ) {
2750
+ this.rpcUrl = rpcUrl;
2751
+ this.noteTransportUrl = noteTransportUrl;
2752
+ this.seed = seed;
2753
+ this.storeName = storeName;
2754
+ this.getKeyCb = getKeyCb;
2755
+ this.insertKeyCb = insertKeyCb;
2756
+ this.signCb = signCb;
2757
+ this.logLevel = logLevel;
2758
+ this.useWorker = useWorker !== false;
2759
+
2760
+ // Check if Web Workers are available AND the caller didn't opt out via
2761
+ // `useWorker: false`. The opt-out is load-bearing for `CallbackProver`
2762
+ // consumers — see the constructor doc above.
2763
+ if (this.useWorker && typeof Worker !== "undefined") {
2764
+ console.log("WebClient: Web Workers are available.");
2765
+ // Pick between the module and classic worker variants at runtime — see
2766
+ // `WebClient.workerMode` below. Both branches keep the
2767
+ // `new Worker(new URL("...", import.meta.url), ...)` form fully literal:
2768
+ // webpack 5's new-worker detector is PURELY SYNTACTIC and only triggers
2769
+ // a proper worker sub-compilation (with asset+chunk tracing into the
2770
+ // Cargo glue and the sibling WASM) when it sees that exact pattern
2771
+ // spelled inline. Hoisting either URL into a variable downgrades the
2772
+ // detection to a plain "copy file as asset" — which in turn makes the
2773
+ // worker's `await import("./Cargo-*.js")` 404 because webpack never
2774
+ // emitted a chunk for it. The bit of duplication here is load-bearing.
2775
+ //
2776
+ // - module (`.module.js` with `{ type: "module" }`): `import.meta.url`
2777
+ // inside the Cargo glue is preserved so webpack/Vite can resolve the
2778
+ // WASM URL statically. Preferred everywhere EXCEPT Safari/WKWebView.
2779
+ // - classic (`.js`, no options): self-contained async IIFE with
2780
+ // `import.meta.url` rewritten to `self.location.href`; the only form
2781
+ // Safari/WKWebView can cold-start in a reasonable time.
2782
+ if (WebClient._shouldUseClassicWorker()) {
2783
+ this.worker = new Worker(
2784
+ new URL("./workers/web-client-methods-worker.js", import.meta.url)
2785
+ );
2786
+ } else {
2787
+ this.worker = new Worker(
2788
+ new URL(
2789
+ "./workers/web-client-methods-worker.module.js",
2790
+ import.meta.url
2791
+ ),
2792
+ { type: "module" }
2793
+ );
2794
+ }
2795
+
2796
+ // Map to track pending worker requests.
2797
+ this.pendingRequests = new Map();
2798
+
2799
+ // Promises to track when the worker script is loaded and ready.
2800
+ this.loaded = new Promise((resolve) => {
2801
+ this.loadedResolver = resolve;
2802
+ });
2803
+
2804
+ // Create a promise that resolves when the worker signals that it is
2805
+ // fully initialized, and rejects if initialization fails. Every
2806
+ // worker-forwarded method awaits `ready` first, so an init failure must
2807
+ // reject it — otherwise those calls would await a promise that never
2808
+ // settles and hang forever.
2809
+ this.ready = new Promise((resolve, reject) => {
2810
+ this.readyResolver = resolve;
2811
+ this.readyRejecter = reject;
2812
+ });
2813
+ // Init can fail before any caller awaits `ready`; this no-op handler
2814
+ // suppresses the unhandledrejection event without consuming the
2815
+ // rejection for real awaiters.
2816
+ this.ready.catch(() => {});
2817
+
2818
+ // Listen for messages from the worker.
2819
+ this.worker.addEventListener("message", async (event) => {
2820
+ const data = event.data;
2821
+
2822
+ // Worker script loaded.
2823
+ if (data.loaded) {
2824
+ this.loadedResolver();
2825
+ return;
2826
+ }
2827
+
2828
+ // Worker ready.
2829
+ if (data.ready) {
2830
+ this.readyResolver();
2831
+ return;
2832
+ }
2833
+
2834
+ if (data.action === WorkerAction.EXECUTE_CALLBACK) {
2835
+ const { callbackType, args, requestId } = data;
2836
+ try {
2837
+ const callbackMapping = {
2838
+ [CallbackType.GET_KEY]: this.getKeyCb,
2839
+ [CallbackType.INSERT_KEY]: this.insertKeyCb,
2840
+ [CallbackType.SIGN]: this.signCb,
2841
+ };
2842
+ if (!callbackMapping[callbackType]) {
2843
+ throw new Error(`Callback ${callbackType} not available`);
2844
+ }
2845
+ const callbackFunction = callbackMapping[callbackType];
2846
+ let result = callbackFunction.apply(this, args);
2847
+ if (result instanceof Promise) {
2848
+ result = await result;
2849
+ }
2850
+
2851
+ this.worker.postMessage({
2852
+ callbackResult: result,
2853
+ callbackRequestId: requestId,
2854
+ });
2855
+ } catch (error) {
2856
+ this.worker.postMessage({
2857
+ callbackError: error.message,
2858
+ callbackRequestId: requestId,
2859
+ });
2860
+ }
2861
+ return;
2862
+ }
2863
+
2864
+ // Handle responses for method calls.
2865
+ const { requestId, error, result, methodName } = data;
2866
+ if (requestId && this.pendingRequests.has(requestId)) {
2867
+ const { resolve, reject } = this.pendingRequests.get(requestId);
2868
+ this.pendingRequests.delete(requestId);
2869
+ if (error) {
2870
+ const workerError =
2871
+ error instanceof Error ? error : deserializeError(error);
2872
+ console.error(
2873
+ `WebClient: Error from worker in ${methodName}:`,
2874
+ workerError
2875
+ );
2876
+ reject(workerError);
2877
+ } else {
2878
+ resolve(result);
2879
+ }
2880
+ return;
2881
+ }
2882
+
2883
+ // An error with no request attached comes from worker initialization
2884
+ // (INIT is the only requestId-less action that can fail). Reject
2885
+ // `ready` so queued and future method calls fail with the real cause
2886
+ // instead of awaiting forever.
2887
+ if (error && !requestId) {
2888
+ const workerError =
2889
+ error instanceof Error ? error : deserializeError(error);
2890
+ console.error(
2891
+ "WebClient: worker initialization failed:",
2892
+ workerError
2893
+ );
2894
+ this.readyRejecter(workerError);
2895
+ }
2896
+ });
2897
+
2898
+ // Once the worker script has loaded, initialize the worker.
2899
+ this.loaded.then(() => this.initializeWorker());
2900
+ } else {
2901
+ console.log(
2902
+ this.useWorker
2903
+ ? "WebClient: Web Workers are not available."
2904
+ : "WebClient: Web Worker shim disabled by caller (useWorker=false)."
2905
+ );
2906
+ // Worker not available or explicitly disabled; set up fallback values.
2907
+ this.worker = null;
2908
+ this.pendingRequests = null;
2909
+ this.loaded = Promise.resolve();
2910
+ this.ready = Promise.resolve();
2911
+ }
2912
+
2913
+ // Lazy initialize the underlying WASM WebClient when first requested.
2914
+ this.wasmWebClient = null;
2915
+ this.wasmWebClientPromise = null;
2916
+
2917
+ // Promise chain to serialize direct WASM calls that require exclusive
2918
+ // (&mut self) access. Without this, concurrent calls on the same client
2919
+ // would panic with "recursive use of an object detected" due to
2920
+ // wasm-bindgen's internal RefCell.
2921
+ this._wasmCallChain = Promise.resolve();
2922
+ // Depth counter for `_withInnerWebClient` re-entrancy. While > 0,
2923
+ // `_serializeWasmCall` runs its callback inline instead of queueing
2924
+ // it on the chain — see the comment on `_serializeWasmCall` for the
2925
+ // safety contract.
2926
+ this._withInnerLockDepth = 0;
2927
+ }
2928
+
2929
+ /**
2930
+ * Serialize a WASM call that requires exclusive (&mut self) access.
2931
+ * Concurrent calls are queued and executed one at a time.
2932
+ *
2933
+ * Wraps both the direct (in-thread) path and the worker-dispatched path.
2934
+ * On the worker path this is redundant with the worker's own message queue,
2935
+ * but harmless (the chain resolves immediately on the main thread once the
2936
+ * worker's postMessage returns). On the direct path it is load-bearing —
2937
+ * without it, concurrent main-thread callers would panic with
2938
+ * "recursive use of an object detected" (wasm-bindgen's internal RefCell).
2939
+ *
2940
+ * Re-entrancy: when invoked from inside a `_withInnerWebClient(fn)`
2941
+ * callback — detected via `_withInnerLockDepth > 0` — `fn` runs inline
2942
+ * (no chain enqueue). The outer `_withInnerWebClient` invocation
2943
+ * already holds the chain via its own wrapping `_serializeWasmCall`,
2944
+ * so enqueueing the inner call would deadlock (the inner queues
2945
+ * behind the outer; the outer awaits the inner). The inline run is
2946
+ * safe because the chain still serializes against external callers
2947
+ * — they queue behind the outer call's chain slot, which only resolves
2948
+ * after `fn` (including all inline re-entries) settles. Callers of
2949
+ * `_withInnerWebClient` MUST hold an external mutex preventing
2950
+ * concurrent access via other code paths on this same instance during
2951
+ * the callback; without that, an external task running between two
2952
+ * awaits inside `fn` would race wasm-bindgen's borrow check.
2953
+ *
2954
+ * @param {() => Promise<any>} fn - The async function to execute.
2955
+ * @returns {Promise<any>} The result of fn.
2956
+ */
2957
+ _serializeWasmCall(fn) {
2958
+ if (this._withInnerLockDepth > 0) {
2959
+ return Promise.resolve().then(fn);
2960
+ }
2961
+ const result = this._wasmCallChain.catch(() => {}).then(fn);
2962
+ this._wasmCallChain = result.catch(() => {});
2963
+ return result;
2964
+ }
2965
+
2966
+ /**
2967
+ * Returns a promise that resolves once every serialized WASM call that
2968
+ * was already on `_wasmCallChain` when `waitForIdle()` was called has
2969
+ * settled. Use this from callers that need to perform a non-WASM-side
2970
+ * action (e.g. clear an in-memory auth key) AFTER any in-flight
2971
+ * execute / submit / sync has completed, so the WASM kernel's auth
2972
+ * callback doesn't race with the key being cleared.
2973
+ *
2974
+ * Does NOT wait for calls enqueued after `waitForIdle()` returns —
2975
+ * this is intentional, so a caller can drain and then proceed without
2976
+ * being blocked indefinitely by a concurrent workload.
2977
+ *
2978
+ * Caveat for `syncState`: `syncStateWithTimeout` awaits
2979
+ * `acquireSyncLock` (Web Locks) BEFORE wrapping its WASM call in
2980
+ * `_serializeWasmCall`, so a sync that is queued on the sync lock but
2981
+ * has not yet reached its WASM phase is not on the chain and will not
2982
+ * be awaited. Every other serialized method (`executeTransaction`,
2983
+ * `newWallet`, `submitNewTransaction`, `proveTransaction`,
2984
+ * `applyTransaction`, and the proxy-fallback reads) routes through
2985
+ * the chain synchronously on call and is always observed.
2986
+ *
2987
+ * @returns {Promise<void>}
2988
+ */
2989
+ async waitForIdle() {
2990
+ // Chain on `_wasmCallChain`; by the time this resolves, any in-flight
2991
+ // serialized call has settled. Catch so the chain state doesn't leak.
2992
+ await this._wasmCallChain.catch(() => {});
2993
+ }
2994
+
2995
+ // TODO: This will soon conflict with some changes in main.
2996
+ // More context here:
2997
+ // https://github.com/0xMiden/miden-client/pull/1645?notification_referrer_id=NT_kwHOA1yg7NoAJVJlcG9zaXRvcnk7NjU5MzQzNzAyO0lzc3VlOzM3OTY4OTU1Nzk&notifications_query=is%3Aunread#discussion_r2696075480
2998
+ initializeWorker() {
2999
+ // Pass `numThreads` to the worker so it can call `wasm.initThreadPool(n)`
3000
+ // inside its OWN WASM instance — the SDK worker's instance is separate
3001
+ // from the main thread's, and rayon's global pool is per-instance.
3002
+ // Default: navigator.hardwareConcurrency (or 1 if unavailable for any
3003
+ // reason — e.g. the page isn't crossOriginIsolated, in which case the
3004
+ // worker will skip pool init and parallelism falls back to sequential).
3005
+ let numThreads = 1;
3006
+ try {
3007
+ if (
3008
+ typeof self !== "undefined" &&
3009
+ self.crossOriginIsolated &&
3010
+ navigator?.hardwareConcurrency
3011
+ ) {
3012
+ numThreads = navigator.hardwareConcurrency;
3013
+ }
3014
+ } catch {}
3015
+ this.worker.postMessage({
3016
+ action: WorkerAction.INIT,
3017
+ args: [
3018
+ this.rpcUrl,
3019
+ this.noteTransportUrl,
3020
+ this.seed,
3021
+ this.storeName,
3022
+ !!this.getKeyCb,
3023
+ !!this.insertKeyCb,
3024
+ !!this.signCb,
3025
+ this.logLevel,
3026
+ numThreads,
3027
+ ],
3028
+ });
3029
+ }
3030
+
3031
+ async getWasmWebClient() {
3032
+ if (this.wasmWebClient) {
3033
+ return this.wasmWebClient;
3034
+ }
3035
+ if (!this.wasmWebClientPromise) {
3036
+ this.wasmWebClientPromise = (async () => {
3037
+ const wasm = await getWasmOrThrow();
3038
+ const client = new wasm.WebClient();
3039
+ this.wasmWebClient = client;
3040
+ return client;
3041
+ })();
3042
+ }
3043
+ return this.wasmWebClientPromise;
3044
+ }
3045
+
3046
+ /**
3047
+ * Factory method to create and initialize a WebClient instance.
3048
+ * This method is async so you can await the asynchronous call to createClient().
3049
+ *
3050
+ * @param {string} rpcUrl - The RPC URL.
3051
+ * @param {string} noteTransportUrl - The note transport URL (optional).
3052
+ * @param {string} seed - The seed for the account.
3053
+ * @param {string | undefined} network - Optional name for the store. Setting this allows multiple clients to be used in the same browser.
3054
+ * @param {string | undefined} logLevel - Optional log verbosity level ("error", "warn", "info", "debug", "trace", "off", or "none").
3055
+ * @param {boolean} [useWorker=true] - When `false`, bypass the Web Worker shim
3056
+ * and run WASM calls on the current thread. Required for `CallbackProver`
3057
+ * consumers (the worker path serializes the prover and loses the callback).
3058
+ * @returns {Promise<WebClient>} The fully initialized WebClient.
3059
+ */
3060
+ static async createClient(
3061
+ rpcUrl,
3062
+ noteTransportUrl,
3063
+ seed,
3064
+ network,
3065
+ logLevel,
3066
+ useWorker = true
3067
+ ) {
3068
+ // Construct the instance (synchronously).
3069
+ const instance = new WebClient(
3070
+ rpcUrl,
3071
+ noteTransportUrl,
3072
+ seed,
3073
+ network,
3074
+ undefined,
3075
+ undefined,
3076
+ undefined,
3077
+ logLevel,
3078
+ useWorker
3079
+ );
3080
+
3081
+ // Set up logging on the main thread before creating the client.
3082
+ if (logLevel) {
3083
+ const wasm = await getWasmOrThrow();
3084
+ wasm.setupLogging(logLevel);
3085
+ }
3086
+
3087
+ // Wait for the underlying wasmWebClient to be initialized.
3088
+ const wasmWebClient = await instance.getWasmWebClient();
3089
+ await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed, network);
3090
+
3091
+ // Wait for the worker to be ready
3092
+ await instance.ready;
3093
+
3094
+ return createClientProxy(instance);
3095
+ }
3096
+
3097
+ /**
3098
+ * Factory method to create and initialize a WebClient instance with a remote keystore.
3099
+ * This method is async so you can await the asynchronous call to createClientWithExternalKeystore().
3100
+ *
3101
+ * @param {string} rpcUrl - The RPC URL.
3102
+ * @param {string | undefined} noteTransportUrl - The note transport URL (optional).
3103
+ * @param {string | undefined} seed - The seed for the account.
3104
+ * @param {string | undefined} storeName - Optional name for the store. Setting this allows multiple clients to be used in the same browser.
3105
+ * @param {Function | undefined} getKeyCb - The get key callback.
3106
+ * @param {Function | undefined} insertKeyCb - The insert key callback.
3107
+ * @param {Function | undefined} signCb - The sign callback.
3108
+ * @param {string | undefined} logLevel - Optional log verbosity level ("error", "warn", "info", "debug", "trace", "off", or "none").
3109
+ * @param {boolean} [useWorker=true] - When `false`, bypass the Web Worker shim
3110
+ * and run WASM calls on the current thread. Required for `CallbackProver`
3111
+ * consumers (the worker path serializes the prover and loses the callback).
3112
+ * @returns {Promise<WebClient>} The fully initialized WebClient.
3113
+ */
3114
+ static async createClientWithExternalKeystore(
3115
+ rpcUrl,
3116
+ noteTransportUrl,
3117
+ seed,
3118
+ storeName,
3119
+ getKeyCb,
3120
+ insertKeyCb,
3121
+ signCb,
3122
+ logLevel,
3123
+ useWorker = true
3124
+ ) {
3125
+ // Construct the instance (synchronously).
3126
+ const instance = new WebClient(
3127
+ rpcUrl,
3128
+ noteTransportUrl,
3129
+ seed,
3130
+ storeName,
3131
+ getKeyCb,
3132
+ insertKeyCb,
3133
+ signCb,
3134
+ logLevel,
3135
+ useWorker
3136
+ );
3137
+
3138
+ // Set up logging on the main thread before creating the client.
3139
+ if (logLevel) {
3140
+ const wasm = await getWasmOrThrow();
3141
+ wasm.setupLogging(logLevel);
3142
+ }
3143
+
3144
+ // Wait for the underlying wasmWebClient to be initialized.
3145
+ const wasmWebClient = await instance.getWasmWebClient();
3146
+ await wasmWebClient.createClientWithExternalKeystore(
3147
+ rpcUrl,
3148
+ noteTransportUrl,
3149
+ seed,
3150
+ storeName,
3151
+ getKeyCb,
3152
+ insertKeyCb,
3153
+ signCb
3154
+ );
3155
+
3156
+ await instance.ready;
3157
+ return createClientProxy(instance);
3158
+ }
3159
+
3160
+ /**
3161
+ * Call a method via the worker.
3162
+ * @param {string} methodName - Name of the method to call.
3163
+ * @param {...any} args - Arguments for the method.
3164
+ * @returns {Promise<any>}
3165
+ */
3166
+ async callMethodWithWorker(methodName, ...args) {
3167
+ await this.ready;
3168
+ // Create a unique request ID.
3169
+ const requestId = `${methodName}-${Date.now()}-${Math.random()}`;
3170
+ return new Promise((resolve, reject) => {
3171
+ // Save the resolve and reject callbacks in the pendingRequests map.
3172
+ this.pendingRequests.set(requestId, { resolve, reject });
3173
+ // Send the method call request to the worker.
3174
+ this.worker.postMessage({
3175
+ action: WorkerAction.CALL_METHOD,
3176
+ methodName,
3177
+ args,
3178
+ requestId,
3179
+ });
3180
+ });
3181
+ }
3182
+
3183
+ // ----- Explicitly Wrapped Methods (Worker-Forwarded) -----
3184
+
3185
+ async newWallet(storageMode, mutable, authSchemeId, seed) {
3186
+ return this._serializeWasmCall(async () => {
3187
+ const wasmWebClient = await this.getWasmWebClient();
3188
+ return await wasmWebClient.newWallet(
3189
+ storageMode,
3190
+ mutable,
3191
+ authSchemeId,
3192
+ seed
3193
+ );
3194
+ });
3195
+ }
3196
+
3197
+ async newFaucet(
3198
+ storageMode,
3199
+ nonFungible,
3200
+ tokenName,
3201
+ tokenSymbol,
3202
+ decimals,
3203
+ maxSupply,
3204
+ authSchemeId
3205
+ ) {
3206
+ return this._serializeWasmCall(async () => {
3207
+ const wasmWebClient = await this.getWasmWebClient();
3208
+ return await wasmWebClient.newFaucet(
3209
+ storageMode,
3210
+ nonFungible,
3211
+ tokenName,
3212
+ tokenSymbol,
3213
+ decimals,
3214
+ maxSupply,
3215
+ authSchemeId
3216
+ );
3217
+ });
3218
+ }
3219
+
3220
+ async newAccount(account, overwrite) {
3221
+ return this._serializeWasmCall(async () => {
3222
+ const wasmWebClient = await this.getWasmWebClient();
3223
+ return await wasmWebClient.newAccount(account, overwrite);
3224
+ });
3225
+ }
3226
+
3227
+ async newAccountWithSecretKey(account, secretKey) {
3228
+ return this._serializeWasmCall(async () => {
3229
+ const wasmWebClient = await this.getWasmWebClient();
3230
+ return await wasmWebClient.newAccountWithSecretKey(account, secretKey);
3231
+ });
3232
+ }
3233
+
3234
+ async submitNewTransaction(accountId, transactionRequest) {
3235
+ try {
3236
+ if (!this.worker) {
3237
+ const wasmWebClient = await this.getWasmWebClient();
3238
+ return await wasmWebClient.submitNewTransaction(
3239
+ accountId,
3240
+ transactionRequest
3241
+ );
3242
+ }
3243
+
3244
+ const wasm = await getWasmOrThrow();
3245
+ const serializedTransactionRequest = transactionRequest.serialize();
3246
+ const result = await this.callMethodWithWorker(
3247
+ MethodName.SUBMIT_NEW_TRANSACTION,
3248
+ accountId.toString(),
3249
+ serializedTransactionRequest
3250
+ );
3251
+
3252
+ const transactionResult = wasm.TransactionResult.deserialize(
3253
+ new Uint8Array(result.serializedTransactionResult)
3254
+ );
3255
+
3256
+ return transactionResult.id();
3257
+ } catch (error) {
3258
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
3259
+ throw error;
3260
+ }
3261
+ }
3262
+
3263
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
3264
+ try {
3265
+ if (!this.worker) {
3266
+ const wasmWebClient = await this.getWasmWebClient();
3267
+ return await wasmWebClient.submitNewTransactionWithProver(
3268
+ accountId,
3269
+ transactionRequest,
3270
+ prover
3271
+ );
3272
+ }
3273
+
3274
+ const wasm = await getWasmOrThrow();
3275
+ const serializedTransactionRequest = transactionRequest.serialize();
3276
+ const proverPayload = prover.serialize();
3277
+ const result = await this.callMethodWithWorker(
3278
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER,
3279
+ accountId.toString(),
3280
+ serializedTransactionRequest,
3281
+ proverPayload
3282
+ );
3283
+
3284
+ const transactionResult = wasm.TransactionResult.deserialize(
3285
+ new Uint8Array(result.serializedTransactionResult)
3286
+ );
3287
+
3288
+ return transactionResult.id();
3289
+ } catch (error) {
3290
+ console.error(
3291
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
3292
+ error
3293
+ );
3294
+ throw error;
3295
+ }
3296
+ }
3297
+
3298
+ async executeTransaction(accountId, transactionRequest) {
3299
+ try {
3300
+ if (!this.worker) {
3301
+ const wasmWebClient = await this.getWasmWebClient();
3302
+ return await wasmWebClient.executeTransaction(
3303
+ accountId,
3304
+ transactionRequest
3305
+ );
3306
+ }
3307
+
3308
+ const wasm = await getWasmOrThrow();
3309
+ const serializedTransactionRequest = transactionRequest.serialize();
3310
+ const serializedResultBytes = await this.callMethodWithWorker(
3311
+ MethodName.EXECUTE_TRANSACTION,
3312
+ accountId.toString(),
3313
+ serializedTransactionRequest
3314
+ );
3315
+
3316
+ return wasm.TransactionResult.deserialize(
3317
+ new Uint8Array(serializedResultBytes)
3318
+ );
3319
+ } catch (error) {
3320
+ console.error("INDEX.JS: Error in executeTransaction:", error);
3321
+ throw error;
3322
+ }
3323
+ }
3324
+
3325
+ async proveTransaction(transactionResult, prover) {
3326
+ try {
3327
+ if (!this.worker) {
3328
+ const wasmWebClient = await this.getWasmWebClient();
3329
+ return await wasmWebClient.proveTransaction(transactionResult, prover);
3330
+ }
3331
+
3332
+ const wasm = await getWasmOrThrow();
3333
+ const serializedTransactionResult = transactionResult.serialize();
3334
+ const proverPayload = prover ? prover.serialize() : null;
3335
+
3336
+ const serializedProvenBytes = await this.callMethodWithWorker(
3337
+ MethodName.PROVE_TRANSACTION,
3338
+ serializedTransactionResult,
3339
+ proverPayload
3340
+ );
3341
+
3342
+ return wasm.ProvenTransaction.deserialize(
3343
+ new Uint8Array(serializedProvenBytes)
3344
+ );
3345
+ } catch (error) {
3346
+ console.error("INDEX.JS: Error in proveTransaction:", error);
3347
+ throw error;
3348
+ }
3349
+ }
3350
+
3351
+ async applyTransaction(transactionResult, submissionHeight) {
3352
+ try {
3353
+ if (!this.worker) {
3354
+ const wasmWebClient = await this.getWasmWebClient();
3355
+ return await wasmWebClient.applyTransaction(
3356
+ transactionResult,
3357
+ submissionHeight
3358
+ );
3359
+ }
3360
+
3361
+ const wasm = await getWasmOrThrow();
3362
+ const serializedTransactionResult = transactionResult.serialize();
3363
+ const serializedUpdateBytes = await this.callMethodWithWorker(
3364
+ MethodName.APPLY_TRANSACTION,
3365
+ serializedTransactionResult,
3366
+ submissionHeight
3367
+ );
3368
+
3369
+ return wasm.TransactionStoreUpdate.deserialize(
3370
+ new Uint8Array(serializedUpdateBytes)
3371
+ );
3372
+ } catch (error) {
3373
+ console.error("INDEX.JS: Error in applyTransaction:", error);
3374
+ throw error;
3375
+ }
3376
+ }
3377
+
3378
+ /**
3379
+ * Syncs the client (NTL followed by chain sync, failing fast on either).
3380
+ *
3381
+ * This method coordinates concurrent sync calls using the Web Locks API when available,
3382
+ * with an in-process mutex fallback for older browsers. If a sync is already in progress,
3383
+ * subsequent callers will wait and receive the same result (coalescing behavior).
3384
+ *
3385
+ * @returns {Promise<SyncSummary>} The sync summary
3386
+ */
3387
+ async syncState() {
3388
+ const dbId = this.storeName || "default";
3389
+ const methodId = MethodName.SYNC_STATE;
3390
+
3391
+ try {
3392
+ return await withSyncLock(dbId, methodId, async () => {
3393
+ if (!this.worker) {
3394
+ const wasmWebClient = await this.getWasmWebClient();
3395
+ return await wasmWebClient.syncStateImpl();
3396
+ }
3397
+ const wasm = await getWasmOrThrow();
3398
+ const serializedSyncSummaryBytes =
3399
+ await this.callMethodWithWorker(methodId);
3400
+ return wasm.SyncSummary.deserialize(
3401
+ new Uint8Array(serializedSyncSummaryBytes)
3402
+ );
3403
+ });
3404
+ } catch (error) {
3405
+ console.error("INDEX.JS: Error in syncState:", error);
3406
+ throw error;
3407
+ }
3408
+ }
3409
+
3410
+ /**
3411
+ * Fetches private notes from the Note Transport Layer.
3412
+ *
3413
+ * @returns {Promise<void>}
3414
+ */
3415
+ async syncNoteTransport() {
3416
+ const dbId = this.storeName || "default";
3417
+ const methodId = MethodName.SYNC_NOTE_TRANSPORT;
3418
+
3419
+ try {
3420
+ await withSyncLock(dbId, methodId, async () => {
3421
+ if (!this.worker) {
3422
+ const wasmWebClient = await this.getWasmWebClient();
3423
+ await wasmWebClient.syncNoteTransportImpl();
3424
+ } else {
3425
+ await this.callMethodWithWorker(methodId);
3426
+ }
3427
+ });
3428
+ } catch (error) {
3429
+ console.error("INDEX.JS: Error in syncNoteTransport:", error);
3430
+ throw error;
3431
+ }
3432
+ }
3433
+
3434
+ /**
3435
+ * Syncs on-chain state only (no NTL fetch).
3436
+ *
3437
+ * @returns {Promise<SyncSummary>}
3438
+ */
3439
+ async syncChain() {
3440
+ const dbId = this.storeName || "default";
3441
+ const methodId = MethodName.SYNC_CHAIN;
3442
+
3443
+ try {
3444
+ return await withSyncLock(dbId, methodId, async () => {
3445
+ if (!this.worker) {
3446
+ const wasmWebClient = await this.getWasmWebClient();
3447
+ return await wasmWebClient.syncChainImpl();
3448
+ }
3449
+ const wasm = await getWasmOrThrow();
3450
+ const serializedSyncSummaryBytes =
3451
+ await this.callMethodWithWorker(methodId);
3452
+ return wasm.SyncSummary.deserialize(
3453
+ new Uint8Array(serializedSyncSummaryBytes)
3454
+ );
3455
+ });
3456
+ } catch (error) {
3457
+ console.error("INDEX.JS: Error in syncChain:", error);
3458
+ throw error;
3459
+ }
3460
+ }
3461
+
3462
+ /**
3463
+ * Terminates the underlying Web Worker used by this WebClient instance.
3464
+ *
3465
+ * Call this method when you're done using a WebClient to free up browser
3466
+ * resources. Each WebClient instance uses a dedicated Web Worker for
3467
+ * computationally intensive operations. Terminating releases that thread.
3468
+ *
3469
+ * After calling terminate(), the WebClient should not be used.
3470
+ */
3471
+ terminate() {
3472
+ if (this.worker) {
3473
+ this.worker.terminate();
3474
+ }
3475
+ }
3476
+ }
3477
+
3478
+ class MockWebClient extends WebClient {
3479
+ constructor(seed, logLevel) {
3480
+ super(
3481
+ null,
3482
+ null,
3483
+ seed,
3484
+ MOCK_STORE_NAME,
3485
+ undefined,
3486
+ undefined,
3487
+ undefined,
3488
+ logLevel
3489
+ );
3490
+ }
3491
+
3492
+ initializeWorker() {
3493
+ // Pass `numThreads` exactly like the real INIT path: every prove runs
3494
+ // inside the worker's own WASM instance, and rayon's pool is
3495
+ // per-instance — without this, mock-client proving (including the
3496
+ // integration suite) silently runs single-threaded.
3497
+ let numThreads = 1;
3498
+ try {
3499
+ if (
3500
+ typeof self !== "undefined" &&
3501
+ self.crossOriginIsolated &&
3502
+ navigator?.hardwareConcurrency
3503
+ ) {
3504
+ numThreads = navigator.hardwareConcurrency;
3505
+ }
3506
+ } catch {}
3507
+ this.worker.postMessage({
3508
+ action: WorkerAction.INIT_MOCK,
3509
+ args: [this.seed, this.logLevel, numThreads],
3510
+ });
3511
+ }
3512
+
3513
+ /**
3514
+ * Factory method to create a WebClient with a mock chain for testing purposes.
3515
+ *
3516
+ * @param serializedMockChain - Serialized mock chain data (optional). Will use an empty chain if not provided.
3517
+ * @param serializedMockNoteTransportNode - Serialized mock note transport node data (optional). Will use a new instance if not provided.
3518
+ * @param seed - The seed for the account (optional).
3519
+ * @returns A promise that resolves to a MockWebClient.
3520
+ */
3521
+ static async createClient(
3522
+ serializedMockChain,
3523
+ serializedMockNoteTransportNode,
3524
+ seed,
3525
+ logLevel
3526
+ ) {
3527
+ // Construct the instance (synchronously).
3528
+ const instance = new MockWebClient(seed, logLevel);
3529
+
3530
+ // Set up logging on the main thread before creating the client.
3531
+ if (logLevel) {
3532
+ const wasm = await getWasmOrThrow();
3533
+ wasm.setupLogging(logLevel);
3534
+ }
3535
+
3536
+ // Wait for the underlying wasmWebClient to be initialized.
3537
+ const wasmWebClient = await instance.getWasmWebClient();
3538
+ await wasmWebClient.createMockClient(
3539
+ seed ?? null,
3540
+ serializedMockChain ?? null,
3541
+ serializedMockNoteTransportNode ?? null
3542
+ );
3543
+
3544
+ // Wait for the worker to be ready
3545
+ await instance.ready;
3546
+
3547
+ return createClientProxy(instance);
3548
+ }
3549
+
3550
+ /**
3551
+ * Syncs the mock client state.
3552
+ *
3553
+ * This method coordinates concurrent sync calls using the Web Locks API when available,
3554
+ * with an in-process mutex fallback for older browsers. If a sync is already in progress,
3555
+ * subsequent callers will wait and receive the same result (coalescing behavior).
3556
+ *
3557
+ * @returns {Promise<SyncSummary>} The sync summary
3558
+ */
3559
+ async syncState() {
3560
+ const dbId = this.storeName || "mock";
3561
+ const methodId = MethodName.SYNC_STATE;
3562
+
3563
+ try {
3564
+ return await withSyncLock(dbId, methodId, async () => {
3565
+ const wasmWebClient = await this.getWasmWebClient();
3566
+
3567
+ if (!this.worker) {
3568
+ return await wasmWebClient.syncStateImpl();
3569
+ }
3570
+
3571
+ const serializedMockChain = (await wasmWebClient.serializeMockChain())
3572
+ .buffer;
3573
+ const serializedMockNoteTransportNode = (
3574
+ await wasmWebClient.serializeMockNoteTransportNode()
3575
+ ).buffer;
3576
+
3577
+ const wasm = await getWasmOrThrow();
3578
+ const serializedSyncSummaryBytes = await this.callMethodWithWorker(
3579
+ MethodName.SYNC_STATE_MOCK,
3580
+ serializedMockChain,
3581
+ serializedMockNoteTransportNode
3582
+ );
3583
+ return wasm.SyncSummary.deserialize(
3584
+ new Uint8Array(serializedSyncSummaryBytes)
3585
+ );
3586
+ });
3587
+ } catch (error) {
3588
+ console.error("INDEX.JS: Error in syncState:", error);
3589
+ throw error;
3590
+ }
3591
+ }
3592
+
3593
+ /**
3594
+ * Syncs only the on-chain mock state (no note transport fetch).
3595
+ *
3596
+ * In worker mode, the main-thread mock chain + note-transport-node state
3597
+ * is serialized and shipped to the worker before the sync, so a prior
3598
+ * `proveBlock()` on the main thread is reflected in the worker's WASM
3599
+ * client. The no-worker path uses the main-thread WASM client directly.
3600
+ *
3601
+ * @returns {Promise<SyncSummary>}
3602
+ */
3603
+ async syncChain() {
3604
+ const dbId = this.storeName || "mock";
3605
+ const methodId = MethodName.SYNC_CHAIN;
3606
+
3607
+ try {
3608
+ return await withSyncLock(dbId, methodId, async () => {
3609
+ const wasmWebClient = await this.getWasmWebClient();
3610
+
3611
+ if (!this.worker) {
3612
+ return await wasmWebClient.syncChainImpl();
3613
+ }
3614
+
3615
+ const serializedMockChain = (await wasmWebClient.serializeMockChain())
3616
+ .buffer;
3617
+ const serializedMockNoteTransportNode = (
3618
+ await wasmWebClient.serializeMockNoteTransportNode()
3619
+ ).buffer;
3620
+
3621
+ const wasm = await getWasmOrThrow();
3622
+ const serializedSyncSummaryBytes = await this.callMethodWithWorker(
3623
+ MethodName.SYNC_CHAIN_MOCK,
3624
+ serializedMockChain,
3625
+ serializedMockNoteTransportNode
3626
+ );
3627
+ return wasm.SyncSummary.deserialize(
3628
+ new Uint8Array(serializedSyncSummaryBytes)
3629
+ );
3630
+ });
3631
+ } catch (error) {
3632
+ console.error("INDEX.JS: Error in syncChain:", error);
3633
+ throw error;
3634
+ }
3635
+ }
3636
+
3637
+ /**
3638
+ * Syncs only the mock note-transport state (no chain fetch).
3639
+ *
3640
+ * Mirrors {@link MockWebClient#syncChain}: in worker mode, the
3641
+ * main-thread mock chain + note-transport-node state is serialized
3642
+ * and shipped to the worker first.
3643
+ *
3644
+ * @returns {Promise<void>}
3645
+ */
3646
+ async syncNoteTransport() {
3647
+ const dbId = this.storeName || "mock";
3648
+ const methodId = MethodName.SYNC_NOTE_TRANSPORT;
3649
+
3650
+ try {
3651
+ await withSyncLock(dbId, methodId, async () => {
3652
+ const wasmWebClient = await this.getWasmWebClient();
3653
+
3654
+ if (!this.worker) {
3655
+ await wasmWebClient.syncNoteTransportImpl();
3656
+ return;
3657
+ }
3658
+
3659
+ const serializedMockChain = (await wasmWebClient.serializeMockChain())
3660
+ .buffer;
3661
+ const serializedMockNoteTransportNode = (
3662
+ await wasmWebClient.serializeMockNoteTransportNode()
3663
+ ).buffer;
3664
+
3665
+ await this.callMethodWithWorker(
3666
+ MethodName.SYNC_NOTE_TRANSPORT_MOCK,
3667
+ serializedMockChain,
3668
+ serializedMockNoteTransportNode
3669
+ );
3670
+ });
3671
+ } catch (error) {
3672
+ console.error("INDEX.JS: Error in syncNoteTransport:", error);
3673
+ throw error;
3674
+ }
3675
+ }
3676
+
3677
+ async submitNewTransaction(accountId, transactionRequest) {
3678
+ try {
3679
+ if (!this.worker) {
3680
+ return await super.submitNewTransaction(accountId, transactionRequest);
3681
+ }
3682
+
3683
+ const wasmWebClient = await this.getWasmWebClient();
3684
+ const wasm = await getWasmOrThrow();
3685
+ const serializedTransactionRequest = transactionRequest.serialize();
3686
+ const serializedMockChain = (await wasmWebClient.serializeMockChain())
3687
+ .buffer;
3688
+ const serializedMockNoteTransportNode = (
3689
+ await wasmWebClient.serializeMockNoteTransportNode()
3690
+ ).buffer;
3691
+
3692
+ const result = await this.callMethodWithWorker(
3693
+ MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
3694
+ accountId.toString(),
3695
+ serializedTransactionRequest,
3696
+ serializedMockChain,
3697
+ serializedMockNoteTransportNode
3698
+ );
3699
+
3700
+ const newMockChain = new Uint8Array(result.serializedMockChain);
3701
+ const newMockNoteTransportNode = result.serializedMockNoteTransportNode
3702
+ ? new Uint8Array(result.serializedMockNoteTransportNode)
3703
+ : undefined;
3704
+
3705
+ const transactionResult = wasm.TransactionResult.deserialize(
3706
+ new Uint8Array(result.serializedTransactionResult)
3707
+ );
3708
+
3709
+ if (!(this instanceof MockWebClient)) {
3710
+ return transactionResult.id();
3711
+ }
3712
+
3713
+ this.wasmWebClient = new wasm.WebClient();
3714
+ this.wasmWebClientPromise = Promise.resolve(this.wasmWebClient);
3715
+ await this.wasmWebClient.createMockClient(
3716
+ this.seed,
3717
+ newMockChain,
3718
+ newMockNoteTransportNode
3719
+ );
3720
+
3721
+ return transactionResult.id();
3722
+ } catch (error) {
3723
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
3724
+ throw error;
3725
+ }
3726
+ }
3727
+
3728
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
3729
+ try {
3730
+ if (!this.worker) {
3731
+ return await super.submitNewTransactionWithProver(
3732
+ accountId,
3733
+ transactionRequest,
3734
+ prover
3735
+ );
3736
+ }
3737
+
3738
+ const wasmWebClient = await this.getWasmWebClient();
3739
+ const wasm = await getWasmOrThrow();
3740
+ const serializedTransactionRequest = transactionRequest.serialize();
3741
+ const proverPayload = prover.serialize();
3742
+ const serializedMockChain = (await wasmWebClient.serializeMockChain())
3743
+ .buffer;
3744
+ const serializedMockNoteTransportNode = (
3745
+ await wasmWebClient.serializeMockNoteTransportNode()
3746
+ ).buffer;
3747
+
3748
+ const result = await this.callMethodWithWorker(
3749
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK,
3750
+ accountId.toString(),
3751
+ serializedTransactionRequest,
3752
+ proverPayload,
3753
+ serializedMockChain,
3754
+ serializedMockNoteTransportNode
3755
+ );
3756
+
3757
+ const newMockChain = new Uint8Array(result.serializedMockChain);
3758
+ const newMockNoteTransportNode = result.serializedMockNoteTransportNode
3759
+ ? new Uint8Array(result.serializedMockNoteTransportNode)
3760
+ : undefined;
3761
+
3762
+ const transactionResult = wasm.TransactionResult.deserialize(
3763
+ new Uint8Array(result.serializedTransactionResult)
3764
+ );
3765
+
3766
+ if (!(this instanceof MockWebClient)) {
3767
+ return transactionResult.id();
3768
+ }
3769
+
3770
+ this.wasmWebClient = new wasm.WebClient();
3771
+ this.wasmWebClientPromise = Promise.resolve(this.wasmWebClient);
3772
+ await this.wasmWebClient.createMockClient(
3773
+ this.seed,
3774
+ newMockChain,
3775
+ newMockNoteTransportNode
3776
+ );
3777
+
3778
+ return transactionResult.id();
3779
+ } catch (error) {
3780
+ console.error(
3781
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
3782
+ error
3783
+ );
3784
+ throw error;
3785
+ }
3786
+ }
3787
+ }
3788
+
3789
+ function copyWebClientStatics(WasmWebClient) {
3790
+ if (!WasmWebClient) {
3791
+ return;
3792
+ }
3793
+ Object.getOwnPropertyNames(WasmWebClient).forEach((prop) => {
3794
+ if (
3795
+ typeof WasmWebClient[prop] === "function" &&
3796
+ prop !== "constructor" &&
3797
+ prop !== "prototype"
3798
+ ) {
3799
+ WebClient[prop] = WasmWebClient[prop];
3800
+ }
3801
+ });
3802
+ }
3803
+
3804
+ // Wire MidenClient dependencies (resolves circular import)
3805
+ MidenClient._WasmWebClient = WebClient;
3806
+ MidenClient._MockWasmWebClient = MockWebClient;
3807
+ MidenClient._getWasmOrThrow = getWasmOrThrow;
3808
+ _setWebClient(WebClient);
3809
+
3810
+ export { AccountType, AuthScheme, CompilerResource, Linking, MidenArrays, MidenClient, MockWebClient as MockWasmWebClient, MockWebClient, NoteVisibility, StorageMode, StorageResult, StorageView, WebClient as WasmWebClient, buildSwapTag, createP2IDENote, createP2IDNote, getWasmOrThrow, withSyncLock, wordToBigInt };
3811
+ //# sourceMappingURL=index.js.map