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

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