@miden-sdk/miden-sdk 0.14.5 → 0.14.9

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