@miden-sdk/miden-sdk 0.13.0-next.1 → 0.13.0-next.3

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.
package/dist/index.js CHANGED
@@ -1,9 +1,17 @@
1
1
  import loadWasm from './wasm.js';
2
- export { Account, AccountArray, AccountBuilder, AccountBuilderResult, AccountCode, AccountComponent, AccountDelta, AccountFile, AccountHeader, AccountId, AccountIdArray, AccountInterface, AccountStorage, AccountStorageDelta, AccountStorageMode, AccountStorageRequirements, AccountType, AccountVaultDelta, Address, AdviceInputs, AdviceMap, AssetVault, AuthSecretKey, BasicFungibleFaucetComponent, BlockHeader, ConsumableNoteRecord, Endpoint, ExecutedTransaction, Felt, FeltArray, FetchedNote, FlattenedU8Vec, ForeignAccount, ForeignAccountArray, FungibleAsset, FungibleAssetDelta, FungibleAssetDeltaItem, GetProceduresResultItem, InputNote, InputNoteRecord, InputNoteState, InputNotes, IntoUnderlyingByteSource, IntoUnderlyingSink, IntoUnderlyingSource, JsAccountUpdate, JsStateSyncUpdate, JsStorageMapEntry, JsStorageSlot, JsVaultAsset, Library, MerklePath, NetworkId, Note, NoteAndArgs, NoteAndArgsArray, NoteAssets, NoteConsumability, NoteDetails, NoteDetailsAndTag, NoteDetailsAndTagArray, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteFilter, NoteFilterTypes, NoteHeader, NoteId, NoteIdAndArgs, NoteIdAndArgsArray, NoteInclusionProof, NoteInputs, NoteLocation, NoteMetadata, NoteRecipient, NoteRecipientArray, NoteScript, NoteTag, NoteType, OutputNote, OutputNoteArray, OutputNotes, OutputNotesArray, Package, PartialNote, Program, ProvenTransaction, PublicKey, RpcClient, Rpo256, ScriptBuilder, SecretKey, SerializedInputNoteData, SerializedOutputNoteData, SerializedTransactionData, Signature, SigningInputs, SigningInputsType, SlotAndKeys, StorageMap, StorageSlot, StorageSlotArray, SyncSummary, TestUtils, TokenSymbol, TransactionArgs, TransactionFilter, TransactionId, TransactionProver, TransactionRecord, TransactionRequest, TransactionRequestBuilder, TransactionResult, TransactionScript, TransactionScriptInputPair, TransactionScriptInputPairArray, TransactionStatus, TransactionStoreUpdate, TransactionSummary, Word, initSync } from './Cargo-15e14c5a.js';
2
+ export { Account, AccountArray, AccountBuilder, AccountBuilderResult, AccountCode, AccountComponent, AccountComponentCode, AccountDelta, AccountFile, AccountHeader, AccountId, AccountIdArray, AccountInterface, AccountStorage, AccountStorageDelta, AccountStorageMode, AccountStorageRequirements, AccountType, AccountVaultDelta, Address, AdviceInputs, AdviceMap, AssetVault, AuthFalcon512RpoMultisigConfig, AuthScheme, AuthSecretKey, BasicFungibleFaucetComponent, BlockHeader, CodeBuilder, CommittedNote, ConsumableNoteRecord, Endpoint, ExecutedTransaction, Felt, FeltArray, FetchedAccount, FetchedNote, FlattenedU8Vec, ForeignAccount, ForeignAccountArray, FungibleAsset, FungibleAssetDelta, FungibleAssetDeltaItem, GetProceduresResultItem, InputNote, InputNoteRecord, InputNoteState, InputNotes, IntoUnderlyingByteSource, IntoUnderlyingSink, IntoUnderlyingSource, JsAccountUpdate, JsStateSyncUpdate, JsStorageMapEntry, JsStorageSlot, JsVaultAsset, Library, MerklePath, NetworkId, NetworkType, Note, NoteAndArgs, NoteAndArgsArray, NoteAssets, NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme, NoteConsumability, NoteConsumptionStatus, NoteDetails, NoteDetailsAndTag, NoteDetailsAndTagArray, NoteExecutionHint, NoteFile, NoteFilter, NoteFilterTypes, NoteHeader, NoteId, NoteIdAndArgs, NoteIdAndArgsArray, NoteInclusionProof, NoteInputs, NoteLocation, NoteMetadata, NoteRecipient, NoteRecipientArray, NoteScript, NoteSyncInfo, NoteTag, NoteType, OutputNote, OutputNoteArray, OutputNoteRecord, OutputNoteState, OutputNotes, OutputNotesArray, Package, PartialNote, ProcedureThreshold, Program, ProvenTransaction, PublicKey, RpcClient, Rpo256, SerializedInputNoteData, SerializedOutputNoteData, SerializedTransactionData, Signature, SigningInputs, SigningInputsType, SlotAndKeys, SparseMerklePath, StorageMap, StorageSlot, StorageSlotArray, SyncSummary, TestUtils, TokenSymbol, TransactionArgs, TransactionFilter, TransactionId, TransactionProver, TransactionRecord, TransactionRequest, TransactionRequestBuilder, TransactionResult, TransactionScript, TransactionScriptInputPair, TransactionScriptInputPairArray, TransactionStatus, TransactionStoreUpdate, TransactionSummary, Word, createAuthFalcon512RpoMultisig, initSync } from './Cargo-eb2eb6d6.js';
3
3
 
4
4
  const WorkerAction = Object.freeze({
5
5
  INIT: "init",
6
+ INIT_MOCK: "initMock",
6
7
  CALL_METHOD: "callMethod",
8
+ EXECUTE_CALLBACK: "executeCallback",
9
+ });
10
+
11
+ const CallbackType = Object.freeze({
12
+ GET_KEY: "getKey",
13
+ INSERT_KEY: "insertKey",
14
+ SIGN: "sign",
7
15
  });
8
16
 
9
17
  const MethodName = Object.freeze({
@@ -14,10 +22,223 @@ const MethodName = Object.freeze({
14
22
  PROVE_TRANSACTION: "proveTransaction",
15
23
  SUBMIT_NEW_TRANSACTION: "submitNewTransaction",
16
24
  SUBMIT_NEW_TRANSACTION_MOCK: "submitNewTransactionMock",
25
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER: "submitNewTransactionWithProver",
26
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK: "submitNewTransactionWithProverMock",
17
27
  SYNC_STATE: "syncState",
18
28
  SYNC_STATE_MOCK: "syncStateMock",
19
29
  });
20
30
 
31
+ /**
32
+ * Sync Lock Module
33
+ *
34
+ * Provides coordination for concurrent syncState() calls using the Web Locks API
35
+ * with an in-process mutex fallback for older browsers.
36
+ *
37
+ * Behavior:
38
+ * - Uses "coalescing": if a sync is in progress, subsequent callers wait and receive
39
+ * the same result
40
+ * - Web Locks for cross-tab coordination (Chrome 69+, Safari 15.4+)
41
+ * - In-process mutex fallback when Web Locks unavailable
42
+ * - Optional timeout support
43
+ */
44
+
45
+ /**
46
+ * Check if the Web Locks API is available.
47
+ */
48
+ function hasWebLocks() {
49
+ return (
50
+ typeof navigator !== "undefined" &&
51
+ navigator.locks !== undefined &&
52
+ typeof navigator.locks.request === "function"
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Internal state for tracking in-progress syncs and waiters per database.
58
+ */
59
+ const syncStates = new Map();
60
+
61
+ /**
62
+ * Get or create sync state for a database.
63
+ */
64
+ function getSyncState(dbId) {
65
+ let state = syncStates.get(dbId);
66
+ if (!state) {
67
+ state = {
68
+ inProgress: false,
69
+ result: null,
70
+ error: null,
71
+ waiters: [],
72
+ releaseLock: null,
73
+ syncGeneration: 0,
74
+ };
75
+ syncStates.set(dbId, state);
76
+ }
77
+ return state;
78
+ }
79
+
80
+ /**
81
+ * Acquire a sync lock for the given database.
82
+ *
83
+ * If a sync is already in progress:
84
+ * - Returns { acquired: false, coalescedResult } after waiting for the result
85
+ *
86
+ * If no sync is in progress:
87
+ * - Returns { acquired: true } and the caller should perform the sync,
88
+ * then call releaseSyncLock() or releaseSyncLockWithError()
89
+ *
90
+ * @param {string} dbId - The database ID to lock
91
+ * @param {number} timeoutMs - Optional timeout in milliseconds (0 = no timeout)
92
+ * @returns {Promise<{acquired: boolean, coalescedResult?: any}>}
93
+ */
94
+ async function acquireSyncLock(dbId, timeoutMs = 0) {
95
+ const state = getSyncState(dbId);
96
+
97
+ // If a sync is already in progress, wait for it to complete (coalescing)
98
+ if (state.inProgress) {
99
+ return new Promise((resolve, reject) => {
100
+ let timeoutId;
101
+ if (timeoutMs > 0) {
102
+ timeoutId = setTimeout(() => {
103
+ const idx = state.waiters.findIndex((w) => w.resolve === onResult);
104
+ if (idx !== -1) {
105
+ state.waiters.splice(idx, 1);
106
+ }
107
+ reject(new Error("Sync lock acquisition timed out"));
108
+ }, timeoutMs);
109
+ }
110
+
111
+ const onResult = (result) => {
112
+ if (timeoutId) clearTimeout(timeoutId);
113
+ resolve({ acquired: false, coalescedResult: result });
114
+ };
115
+
116
+ const onError = (error) => {
117
+ if (timeoutId) clearTimeout(timeoutId);
118
+ reject(error);
119
+ };
120
+
121
+ state.waiters.push({ resolve: onResult, reject: onError });
122
+ });
123
+ }
124
+
125
+ // Mark sync as in progress and increment generation
126
+ state.inProgress = true;
127
+ state.result = null;
128
+ state.error = null;
129
+ state.syncGeneration++;
130
+ const currentGeneration = state.syncGeneration;
131
+
132
+ // Try to acquire Web Lock if available
133
+ if (hasWebLocks()) {
134
+ const lockName = `miden-sync-${dbId}`;
135
+
136
+ return new Promise((resolve, reject) => {
137
+ let timeoutId;
138
+ let timedOut = false;
139
+
140
+ if (timeoutMs > 0) {
141
+ timeoutId = setTimeout(() => {
142
+ timedOut = true;
143
+ if (state.syncGeneration === currentGeneration) {
144
+ state.inProgress = false;
145
+ const error = new Error("Sync lock acquisition timed out");
146
+ for (const waiter of state.waiters) {
147
+ waiter.reject(error);
148
+ }
149
+ state.waiters = [];
150
+ }
151
+ reject(new Error("Sync lock acquisition timed out"));
152
+ }, timeoutMs);
153
+ }
154
+
155
+ navigator.locks
156
+ .request(lockName, { mode: "exclusive" }, async () => {
157
+ if (timedOut || state.syncGeneration !== currentGeneration) {
158
+ return;
159
+ }
160
+
161
+ if (timeoutId) clearTimeout(timeoutId);
162
+
163
+ return new Promise((releaseLock) => {
164
+ state.releaseLock = releaseLock;
165
+ resolve({ acquired: true });
166
+ });
167
+ })
168
+ .catch((err) => {
169
+ if (timeoutId) clearTimeout(timeoutId);
170
+ if (state.syncGeneration === currentGeneration) {
171
+ state.inProgress = false;
172
+ }
173
+ reject(err instanceof Error ? err : new Error(String(err)));
174
+ });
175
+ });
176
+ } else {
177
+ // Fallback: no Web Locks, just use in-process state
178
+ return { acquired: true };
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Release the sync lock with a successful result.
184
+ *
185
+ * This notifies all waiting callers with the result and releases the lock.
186
+ *
187
+ * @param {string} dbId - The database ID
188
+ * @param {any} result - The sync result to pass to waiters
189
+ */
190
+ function releaseSyncLock(dbId, result) {
191
+ const state = getSyncState(dbId);
192
+
193
+ if (!state.inProgress) {
194
+ console.warn("releaseSyncLock called but no sync was in progress");
195
+ return;
196
+ }
197
+
198
+ state.result = result;
199
+ state.inProgress = false;
200
+
201
+ for (const waiter of state.waiters) {
202
+ waiter.resolve(result);
203
+ }
204
+ state.waiters = [];
205
+
206
+ if (state.releaseLock) {
207
+ state.releaseLock();
208
+ state.releaseLock = null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Release the sync lock due to an error.
214
+ *
215
+ * This notifies all waiting callers that the sync failed.
216
+ *
217
+ * @param {string} dbId - The database ID
218
+ * @param {Error} error - The error to pass to waiters
219
+ */
220
+ function releaseSyncLockWithError(dbId, error) {
221
+ const state = getSyncState(dbId);
222
+
223
+ if (!state.inProgress) {
224
+ console.warn("releaseSyncLockWithError called but no sync was in progress");
225
+ return;
226
+ }
227
+
228
+ state.error = error;
229
+ state.inProgress = false;
230
+
231
+ for (const waiter of state.waiters) {
232
+ waiter.reject(error);
233
+ }
234
+ state.waiters = [];
235
+
236
+ if (state.releaseLock) {
237
+ state.releaseLock();
238
+ state.releaseLock = null;
239
+ }
240
+ }
241
+
21
242
  const buildTypedArraysExport = (exportObject) => {
22
243
  return Object.entries(exportObject).reduce(
23
244
  (exports, [exportName, _export]) => {
@@ -30,6 +251,27 @@ const buildTypedArraysExport = (exportObject) => {
30
251
  );
31
252
  };
32
253
 
254
+ const deserializeError = (errorLike) => {
255
+ if (!errorLike) {
256
+ return new Error("Unknown error received from worker");
257
+ }
258
+ const { name, message, stack, cause, ...rest } = errorLike;
259
+ const reconstructedError = new Error(message ?? "Unknown worker error");
260
+ reconstructedError.name = name ?? reconstructedError.name;
261
+ if (stack) {
262
+ reconstructedError.stack = stack;
263
+ }
264
+ if (cause) {
265
+ reconstructedError.cause = deserializeError(cause);
266
+ }
267
+ Object.entries(rest).forEach(([key, value]) => {
268
+ if (value !== undefined) {
269
+ reconstructedError[key] = value;
270
+ }
271
+ });
272
+ return reconstructedError;
273
+ };
274
+
33
275
  const MidenArrays = {};
34
276
 
35
277
  let wasmModule = null;
@@ -96,36 +338,41 @@ class WebClient {
96
338
  *
97
339
  * @param {string | undefined} rpcUrl - RPC endpoint URL used by the client.
98
340
  * @param {Uint8Array | undefined} seed - Optional seed for account initialization.
341
+ * @param {string | undefined} storeName - Optional name for the store to be used by the client.
99
342
  * @param {(pubKey: Uint8Array) => Promise<Uint8Array | null | undefined> | Uint8Array | null | undefined} [getKeyCb]
100
343
  * - Callback to retrieve the secret key bytes for a given public key. The `pubKey`
101
344
  * parameter is the serialized public key (from `PublicKey.serialize()`). Return the
102
345
  * corresponding secret key as a `Uint8Array`, or `null`/`undefined` if not found. The
103
346
  * return value may be provided synchronously or via a `Promise`.
104
- * @param {(pubKey: Uint8Array, secretKey: Uint8Array) => Promise<void> | void} [insertKeyCb]
347
+ * @param {(pubKey: Uint8Array, AuthSecretKey: Uint8Array) => Promise<void> | void} [insertKeyCb]
105
348
  * - Callback to persist a secret key. `pubKey` is the serialized public key, and
106
- * `secretKey` is the serialized secret key (from `SecretKey.serialize()`). May return
349
+ * `authSecretKey` is the serialized secret key (from `AuthSecretKey.serialize()`). May return
107
350
  * `void` or a `Promise<void>`.
108
- * @param {(pubKey: Uint8Array, signingInputs: Uint8Array) => Promise<Array<number | string>> | Array<number | string>} [signCb]
109
- * - Callback to produce signature elements for the provided inputs. `pubKey` is the
351
+ * @param {(pubKey: Uint8Array, signingInputs: Uint8Array) => Promise<Uint8Array> | Uint8Array} [signCb]
352
+ * - Callback to produce serialized signature bytes for the provided inputs. `pubKey` is the
110
353
  * serialized public key, and `signingInputs` is a `Uint8Array` produced by
111
- * `SigningInputs.serialize()`. Must return an array of numeric values (numbers or numeric
112
- * strings) representing the signature elements, either directly or wrapped in a `Promise`.
354
+ * `SigningInputs.serialize()`. Must return a `Uint8Array` containing the serialized
355
+ * signature, either directly or wrapped in a `Promise`.
113
356
  */
114
- constructor(rpcUrl, noteTransportUrl, seed, getKeyCb, insertKeyCb, signCb) {
357
+ constructor(
358
+ rpcUrl,
359
+ noteTransportUrl,
360
+ seed,
361
+ storeName,
362
+ getKeyCb,
363
+ insertKeyCb,
364
+ signCb
365
+ ) {
115
366
  this.rpcUrl = rpcUrl;
116
367
  this.noteTransportUrl = noteTransportUrl;
117
368
  this.seed = seed;
369
+ this.storeName = storeName;
118
370
  this.getKeyCb = getKeyCb;
119
371
  this.insertKeyCb = insertKeyCb;
120
372
  this.signCb = signCb;
121
373
 
122
374
  // Check if Web Workers are available.
123
- if (
124
- typeof Worker !== "undefined" &&
125
- !this.getKeyCb &&
126
- !this.insertKeyCb &&
127
- !this.signCb
128
- ) {
375
+ if (typeof Worker !== "undefined") {
129
376
  console.log("WebClient: Web Workers are available.");
130
377
  // Create the worker.
131
378
  this.worker = new Worker(
@@ -147,7 +394,7 @@ class WebClient {
147
394
  });
148
395
 
149
396
  // Listen for messages from the worker.
150
- this.worker.addEventListener("message", (event) => {
397
+ this.worker.addEventListener("message", async (event) => {
151
398
  const data = event.data;
152
399
 
153
400
  // Worker script loaded.
@@ -162,17 +409,49 @@ class WebClient {
162
409
  return;
163
410
  }
164
411
 
412
+ if (data.action === WorkerAction.EXECUTE_CALLBACK) {
413
+ const { callbackType, args, requestId } = data;
414
+ try {
415
+ const callbackMapping = {
416
+ [CallbackType.GET_KEY]: this.getKeyCb,
417
+ [CallbackType.INSERT_KEY]: this.insertKeyCb,
418
+ [CallbackType.SIGN]: this.signCb,
419
+ };
420
+ if (!callbackMapping[callbackType]) {
421
+ throw new Error(`Callback ${callbackType} not available`);
422
+ }
423
+ const callbackFunction = callbackMapping[callbackType];
424
+ let result = callbackFunction.apply(this, args);
425
+ if (result instanceof Promise) {
426
+ result = await result;
427
+ }
428
+
429
+ this.worker.postMessage({
430
+ callbackResult: result,
431
+ callbackRequestId: requestId,
432
+ });
433
+ } catch (error) {
434
+ this.worker.postMessage({
435
+ callbackError: error.message,
436
+ callbackRequestId: requestId,
437
+ });
438
+ }
439
+ return;
440
+ }
441
+
165
442
  // Handle responses for method calls.
166
443
  const { requestId, error, result, methodName } = data;
167
444
  if (requestId && this.pendingRequests.has(requestId)) {
168
445
  const { resolve, reject } = this.pendingRequests.get(requestId);
169
446
  this.pendingRequests.delete(requestId);
170
447
  if (error) {
448
+ const workerError =
449
+ error instanceof Error ? error : deserializeError(error);
171
450
  console.error(
172
451
  `WebClient: Error from worker in ${methodName}:`,
173
- error
452
+ workerError
174
453
  );
175
- reject(new Error(error));
454
+ reject(workerError);
176
455
  } else {
177
456
  resolve(result);
178
457
  }
@@ -180,19 +459,7 @@ class WebClient {
180
459
  });
181
460
 
182
461
  // Once the worker script has loaded, initialize the worker.
183
- this.loaded.then(() => {
184
- this.worker.postMessage({
185
- action: WorkerAction.INIT,
186
- args: [
187
- this.rpcUrl,
188
- this.noteTransportUrl,
189
- this.seed,
190
- this.getKeyCb,
191
- this.insertKeyCb,
192
- this.signCb,
193
- ],
194
- });
195
- });
462
+ this.loaded.then(() => this.initializeWorker());
196
463
  } else {
197
464
  console.log("WebClient: Web Workers are not available.");
198
465
  // Worker not available; set up fallback values.
@@ -207,6 +474,24 @@ class WebClient {
207
474
  this.wasmWebClientPromise = null;
208
475
  }
209
476
 
477
+ // TODO: This will soon conflict with some changes in main.
478
+ // More context here:
479
+ // https://github.com/0xMiden/miden-client/pull/1645?notification_referrer_id=NT_kwHOA1yg7NoAJVJlcG9zaXRvcnk7NjU5MzQzNzAyO0lzc3VlOzM3OTY4OTU1Nzk&notifications_query=is%3Aunread#discussion_r2696075480
480
+ initializeWorker() {
481
+ this.worker.postMessage({
482
+ action: WorkerAction.INIT,
483
+ args: [
484
+ this.rpcUrl,
485
+ this.noteTransportUrl,
486
+ this.seed,
487
+ this.storeName,
488
+ !!this.getKeyCb,
489
+ !!this.insertKeyCb,
490
+ !!this.signCb,
491
+ ],
492
+ });
493
+ }
494
+
210
495
  async getWasmWebClient() {
211
496
  if (this.wasmWebClient) {
212
497
  return this.wasmWebClient;
@@ -229,15 +514,16 @@ class WebClient {
229
514
  * @param {string} rpcUrl - The RPC URL.
230
515
  * @param {string} noteTransportUrl - The note transport URL (optional).
231
516
  * @param {string} seed - The seed for the account.
517
+ * @param {string | undefined} network - Optional name for the store. Setting this allows multiple clients to be used in the same browser.
232
518
  * @returns {Promise<WebClient>} The fully initialized WebClient.
233
519
  */
234
- static async createClient(rpcUrl, noteTransportUrl, seed) {
520
+ static async createClient(rpcUrl, noteTransportUrl, seed, network) {
235
521
  // Construct the instance (synchronously).
236
- const instance = new WebClient(rpcUrl, noteTransportUrl, seed);
522
+ const instance = new WebClient(rpcUrl, noteTransportUrl, seed, network);
237
523
 
238
524
  // Wait for the underlying wasmWebClient to be initialized.
239
525
  const wasmWebClient = await instance.getWasmWebClient();
240
- await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed);
526
+ await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed, network);
241
527
 
242
528
  // Wait for the worker to be ready
243
529
  await instance.ready;
@@ -269,6 +555,7 @@ class WebClient {
269
555
  * @param {string} rpcUrl - The RPC URL.
270
556
  * @param {string | undefined} noteTransportUrl - The note transport URL (optional).
271
557
  * @param {string | undefined} seed - The seed for the account.
558
+ * @param {string | undefined} storeName - Optional name for the store. Setting this allows multiple clients to be used in the same browser.
272
559
  * @param {Function | undefined} getKeyCb - The get key callback.
273
560
  * @param {Function | undefined} insertKeyCb - The insert key callback.
274
561
  * @param {Function | undefined} signCb - The sign callback.
@@ -278,6 +565,7 @@ class WebClient {
278
565
  rpcUrl,
279
566
  noteTransportUrl,
280
567
  seed,
568
+ storeName,
281
569
  getKeyCb,
282
570
  insertKeyCb,
283
571
  signCb
@@ -287,19 +575,23 @@ class WebClient {
287
575
  rpcUrl,
288
576
  noteTransportUrl,
289
577
  seed,
578
+ storeName,
290
579
  getKeyCb,
291
580
  insertKeyCb,
292
581
  signCb
293
582
  );
583
+ // Wait for the underlying wasmWebClient to be initialized.
294
584
  const wasmWebClient = await instance.getWasmWebClient();
295
585
  await wasmWebClient.createClientWithExternalKeystore(
296
586
  rpcUrl,
297
587
  noteTransportUrl,
298
588
  seed,
589
+ storeName,
299
590
  getKeyCb,
300
591
  insertKeyCb,
301
592
  signCb
302
593
  );
594
+
303
595
  await instance.ready;
304
596
  // Return a proxy that forwards missing properties to wasmWebClient.
305
597
  return new Proxy(instance, {
@@ -368,7 +660,7 @@ class WebClient {
368
660
  );
369
661
  return wasm.Account.deserialize(new Uint8Array(serializedAccountBytes));
370
662
  } catch (error) {
371
- console.error("INDEX.JS: Error in newWallet:", error.toString());
663
+ console.error("INDEX.JS: Error in newWallet:", error);
372
664
  throw error;
373
665
  }
374
666
  }
@@ -408,7 +700,7 @@ class WebClient {
408
700
 
409
701
  return wasm.Account.deserialize(new Uint8Array(serializedAccountBytes));
410
702
  } catch (error) {
411
- console.error("INDEX.JS: Error in newFaucet:", error.toString());
703
+ console.error("INDEX.JS: Error in newFaucet:", error);
412
704
  throw error;
413
705
  }
414
706
  }
@@ -437,83 +729,69 @@ class WebClient {
437
729
 
438
730
  return transactionResult.id();
439
731
  } catch (error) {
440
- console.error(
441
- "INDEX.JS: Error in submitNewTransaction:",
442
- error.toString()
443
- );
732
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
444
733
  throw error;
445
734
  }
446
735
  }
447
736
 
448
- async executeTransaction(accountId, transactionRequest) {
737
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
449
738
  try {
450
739
  if (!this.worker) {
451
740
  const wasmWebClient = await this.getWasmWebClient();
452
- return await wasmWebClient.executeTransaction(
741
+ return await wasmWebClient.submitNewTransactionWithProver(
453
742
  accountId,
454
- transactionRequest
743
+ transactionRequest,
744
+ prover
455
745
  );
456
746
  }
457
747
 
458
748
  const wasm = await getWasmOrThrow();
459
749
  const serializedTransactionRequest = transactionRequest.serialize();
460
- const serializedResultBytes = await this.callMethodWithWorker(
461
- MethodName.EXECUTE_TRANSACTION,
750
+ const proverPayload = prover.serialize();
751
+ const result = await this.callMethodWithWorker(
752
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER,
462
753
  accountId.toString(),
463
- serializedTransactionRequest
754
+ serializedTransactionRequest,
755
+ proverPayload
464
756
  );
465
757
 
466
- return wasm.TransactionResult.deserialize(
467
- new Uint8Array(serializedResultBytes)
758
+ const transactionResult = wasm.TransactionResult.deserialize(
759
+ new Uint8Array(result.serializedTransactionResult)
468
760
  );
761
+
762
+ return transactionResult.id();
469
763
  } catch (error) {
470
- console.error("INDEX.JS: Error in executeTransaction:", error.toString());
764
+ console.error(
765
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
766
+ error
767
+ );
471
768
  throw error;
472
769
  }
473
770
  }
474
771
 
475
- async submitTransaction(transactionResult, prover) {
772
+ async executeTransaction(accountId, transactionRequest) {
476
773
  try {
477
774
  if (!this.worker) {
478
775
  const wasmWebClient = await this.getWasmWebClient();
479
- const proven = await wasmWebClient.proveTransaction(
480
- transactionResult,
481
- prover
482
- );
483
- const submissionHeight = await wasmWebClient.submitProvenTransaction(
484
- proven,
485
- transactionResult
486
- );
487
- return await wasmWebClient.applyTransaction(
488
- transactionResult,
489
- submissionHeight
776
+ return await wasmWebClient.executeTransaction(
777
+ accountId,
778
+ transactionRequest
490
779
  );
491
780
  }
492
781
 
493
782
  const wasm = await getWasmOrThrow();
494
- const serializedTransactionResult = transactionResult.serialize();
495
- const proverPayload = prover ? prover.serialize() : null;
496
-
497
- const { submissionHeight, serializedTransactionUpdate } =
498
- await this.callMethodWithWorker(
499
- MethodName.SUBMIT_TRANSACTION,
500
- serializedTransactionResult,
501
- proverPayload
502
- );
503
-
504
- if (this instanceof MockWebClient) {
505
- return wasm.TransactionStoreUpdate.deserialize(
506
- new Uint8Array(serializedTransactionUpdate)
507
- );
508
- }
783
+ const serializedTransactionRequest = transactionRequest.serialize();
784
+ const serializedResultBytes = await this.callMethodWithWorker(
785
+ MethodName.EXECUTE_TRANSACTION,
786
+ accountId.toString(),
787
+ serializedTransactionRequest
788
+ );
509
789
 
510
- const wasmWebClient = await this.getWasmWebClient();
511
- return await wasmWebClient.applyTransaction(
512
- transactionResult,
513
- submissionHeight
790
+ return wasm.TransactionResult.deserialize(
791
+ new Uint8Array(serializedResultBytes)
514
792
  );
515
793
  } catch (error) {
516
- console.error("INDEX.JS: Error in submitTransaction:", error.toString());
794
+ console.error("INDEX.JS: Error in executeTransaction:", error);
517
795
  throw error;
518
796
  }
519
797
  }
@@ -539,28 +817,73 @@ class WebClient {
539
817
  new Uint8Array(serializedProvenBytes)
540
818
  );
541
819
  } catch (error) {
542
- console.error("INDEX.JS: Error in proveTransaction:", error.toString());
820
+ console.error("INDEX.JS: Error in proveTransaction:", error);
543
821
  throw error;
544
822
  }
545
823
  }
546
824
 
825
+ /**
826
+ * Syncs the client state with the node.
827
+ *
828
+ * This method coordinates concurrent sync calls using the Web Locks API when available,
829
+ * with an in-process mutex fallback for older browsers. If a sync is already in progress,
830
+ * subsequent callers will wait and receive the same result (coalescing behavior).
831
+ *
832
+ * @returns {Promise<SyncSummary>} The sync summary
833
+ */
547
834
  async syncState() {
835
+ return this.syncStateWithTimeout(0);
836
+ }
837
+
838
+ /**
839
+ * Syncs the client state with the node with an optional timeout.
840
+ *
841
+ * This method coordinates concurrent sync calls using the Web Locks API when available,
842
+ * with an in-process mutex fallback for older browsers. If a sync is already in progress,
843
+ * subsequent callers will wait and receive the same result (coalescing behavior).
844
+ *
845
+ * @param {number} timeoutMs - Timeout in milliseconds (0 = no timeout)
846
+ * @returns {Promise<SyncSummary>} The sync summary
847
+ */
848
+ async syncStateWithTimeout(timeoutMs = 0) {
849
+ // Use storeName as the database ID for lock coordination
850
+ const dbId = this.storeName || "default";
851
+
548
852
  try {
549
- if (!this.worker) {
550
- const wasmWebClient = await this.getWasmWebClient();
551
- return await wasmWebClient.syncState();
853
+ // Acquire the sync lock (coordinates concurrent calls)
854
+ const lockHandle = await acquireSyncLock(dbId, timeoutMs);
855
+
856
+ if (!lockHandle.acquired) {
857
+ // We're coalescing - return the result from the in-progress sync
858
+ return lockHandle.coalescedResult;
552
859
  }
553
860
 
554
- const wasm = await getWasmOrThrow();
555
- const serializedSyncSummaryBytes = await this.callMethodWithWorker(
556
- MethodName.SYNC_STATE
557
- );
861
+ // We acquired the lock - perform the sync
862
+ try {
863
+ let result;
864
+ if (!this.worker) {
865
+ const wasmWebClient = await this.getWasmWebClient();
866
+ result = await wasmWebClient.syncStateImpl();
867
+ } else {
868
+ const wasm = await getWasmOrThrow();
869
+ const serializedSyncSummaryBytes = await this.callMethodWithWorker(
870
+ MethodName.SYNC_STATE
871
+ );
872
+ result = wasm.SyncSummary.deserialize(
873
+ new Uint8Array(serializedSyncSummaryBytes)
874
+ );
875
+ }
558
876
 
559
- return wasm.SyncSummary.deserialize(
560
- new Uint8Array(serializedSyncSummaryBytes)
561
- );
877
+ // Release the lock with the result
878
+ releaseSyncLock(dbId, result);
879
+ return result;
880
+ } catch (error) {
881
+ // Release the lock with the error
882
+ releaseSyncLockWithError(dbId, error);
883
+ throw error;
884
+ }
562
885
  } catch (error) {
563
- console.error("INDEX.JS: Error in syncState:", error.toString());
886
+ console.error("INDEX.JS: Error in syncState:", error);
564
887
  throw error;
565
888
  }
566
889
  }
@@ -574,7 +897,14 @@ class WebClient {
574
897
 
575
898
  class MockWebClient extends WebClient {
576
899
  constructor(seed) {
577
- super(null, seed);
900
+ super(null, null, seed, "mock");
901
+ }
902
+
903
+ initializeWorker() {
904
+ this.worker.postMessage({
905
+ action: WorkerAction.INIT_MOCK,
906
+ args: [this.seed],
907
+ });
578
908
  }
579
909
 
580
910
  /**
@@ -624,65 +954,103 @@ class MockWebClient extends WebClient {
624
954
  });
625
955
  }
626
956
 
957
+ /**
958
+ * Syncs the mock client state.
959
+ *
960
+ * This method coordinates concurrent sync calls using the Web Locks API when available,
961
+ * with an in-process mutex fallback for older browsers. If a sync is already in progress,
962
+ * subsequent callers will wait and receive the same result (coalescing behavior).
963
+ *
964
+ * @returns {Promise<SyncSummary>} The sync summary
965
+ */
627
966
  async syncState() {
967
+ return this.syncStateWithTimeout(0);
968
+ }
969
+
970
+ /**
971
+ * Syncs the mock client state with an optional timeout.
972
+ *
973
+ * @param {number} timeoutMs - Timeout in milliseconds (0 = no timeout)
974
+ * @returns {Promise<SyncSummary>} The sync summary
975
+ */
976
+ async syncStateWithTimeout(timeoutMs = 0) {
977
+ const dbId = this.storeName || "mock";
978
+
628
979
  try {
629
- const wasmWebClient = await this.getWasmWebClient();
630
- if (!this.worker) {
631
- return await wasmWebClient.syncState();
980
+ const lockHandle = await acquireSyncLock(dbId, timeoutMs);
981
+
982
+ if (!lockHandle.acquired) {
983
+ return lockHandle.coalescedResult;
632
984
  }
633
985
 
634
- let serializedMockChain = wasmWebClient.serializeMockChain().buffer;
635
- let serializedMockNoteTransportNode =
636
- wasmWebClient.serializeMockNoteTransportNode().buffer;
986
+ try {
987
+ let result;
988
+ const wasmWebClient = await this.getWasmWebClient();
637
989
 
638
- const wasm = await getWasmOrThrow();
990
+ if (!this.worker) {
991
+ result = await wasmWebClient.syncStateImpl();
992
+ } else {
993
+ let serializedMockChain = wasmWebClient.serializeMockChain().buffer;
994
+ let serializedMockNoteTransportNode =
995
+ wasmWebClient.serializeMockNoteTransportNode().buffer;
639
996
 
640
- const serializedSyncSummaryBytes = await this.callMethodWithWorker(
641
- MethodName.SYNC_STATE_MOCK,
642
- serializedMockChain,
643
- serializedMockNoteTransportNode
644
- );
997
+ const wasm = await getWasmOrThrow();
645
998
 
646
- return wasm.SyncSummary.deserialize(
647
- new Uint8Array(serializedSyncSummaryBytes)
648
- );
999
+ const serializedSyncSummaryBytes = await this.callMethodWithWorker(
1000
+ MethodName.SYNC_STATE_MOCK,
1001
+ serializedMockChain,
1002
+ serializedMockNoteTransportNode
1003
+ );
1004
+
1005
+ result = wasm.SyncSummary.deserialize(
1006
+ new Uint8Array(serializedSyncSummaryBytes)
1007
+ );
1008
+ }
1009
+
1010
+ releaseSyncLock(dbId, result);
1011
+ return result;
1012
+ } catch (error) {
1013
+ releaseSyncLockWithError(dbId, error);
1014
+ throw error;
1015
+ }
649
1016
  } catch (error) {
650
- console.error("INDEX.JS: Error in syncState:", error.toString());
1017
+ console.error("INDEX.JS: Error in syncState:", error);
651
1018
  throw error;
652
1019
  }
653
1020
  }
654
1021
 
655
- async submitTransaction(transactionResult, prover) {
1022
+ async submitNewTransaction(accountId, transactionRequest) {
656
1023
  try {
657
1024
  if (!this.worker) {
658
- return await super.submitTransaction(transactionResult, prover);
1025
+ return await super.submitNewTransaction(accountId, transactionRequest);
659
1026
  }
660
1027
 
661
1028
  const wasmWebClient = await this.getWasmWebClient();
662
1029
  const wasm = await getWasmOrThrow();
663
- const serializedTransactionResult = transactionResult.serialize();
664
- const proverPayload = prover ? prover.serialize() : null;
1030
+ const serializedTransactionRequest = transactionRequest.serialize();
665
1031
  const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
666
1032
  const serializedMockNoteTransportNode =
667
1033
  wasmWebClient.serializeMockNoteTransportNode().buffer;
668
1034
 
669
1035
  const result = await this.callMethodWithWorker(
670
- MethodName.SUBMIT_TRANSACTION_MOCK,
671
- serializedTransactionResult,
672
- proverPayload,
1036
+ MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
1037
+ accountId.toString(),
1038
+ serializedTransactionRequest,
673
1039
  serializedMockChain,
674
1040
  serializedMockNoteTransportNode
675
1041
  );
1042
+
676
1043
  const newMockChain = new Uint8Array(result.serializedMockChain);
677
1044
  const newMockNoteTransportNode = result.serializedMockNoteTransportNode
678
1045
  ? new Uint8Array(result.serializedMockNoteTransportNode)
679
1046
  : undefined;
680
1047
 
1048
+ const transactionResult = wasm.TransactionResult.deserialize(
1049
+ new Uint8Array(result.serializedTransactionResult)
1050
+ );
1051
+
681
1052
  if (!(this instanceof MockWebClient)) {
682
- return await wasmWebClient.applyTransaction(
683
- transactionResult,
684
- result.submissionHeight
685
- );
1053
+ return transactionResult.id();
686
1054
  }
687
1055
 
688
1056
  this.wasmWebClient = new wasm.WebClient();
@@ -693,32 +1061,36 @@ class MockWebClient extends WebClient {
693
1061
  newMockNoteTransportNode
694
1062
  );
695
1063
 
696
- return wasm.TransactionStoreUpdate.deserialize(
697
- new Uint8Array(result.serializedTransactionUpdate)
698
- );
1064
+ return transactionResult.id();
699
1065
  } catch (error) {
700
- console.error("INDEX.JS: Error in submitTransaction:", error.toString());
1066
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
701
1067
  throw error;
702
1068
  }
703
1069
  }
704
1070
 
705
- async submitNewTransaction(accountId, transactionRequest) {
1071
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
706
1072
  try {
707
1073
  if (!this.worker) {
708
- return await super.submitNewTransaction(accountId, transactionRequest);
1074
+ return await super.submitNewTransactionWithProver(
1075
+ accountId,
1076
+ transactionRequest,
1077
+ prover
1078
+ );
709
1079
  }
710
1080
 
711
1081
  const wasmWebClient = await this.getWasmWebClient();
712
1082
  const wasm = await getWasmOrThrow();
713
1083
  const serializedTransactionRequest = transactionRequest.serialize();
1084
+ const proverPayload = prover.serialize();
714
1085
  const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
715
1086
  const serializedMockNoteTransportNode =
716
1087
  wasmWebClient.serializeMockNoteTransportNode().buffer;
717
1088
 
718
1089
  const result = await this.callMethodWithWorker(
719
- MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
1090
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK,
720
1091
  accountId.toString(),
721
1092
  serializedTransactionRequest,
1093
+ proverPayload,
722
1094
  serializedMockChain,
723
1095
  serializedMockNoteTransportNode
724
1096
  );
@@ -747,8 +1119,8 @@ class MockWebClient extends WebClient {
747
1119
  return transactionResult.id();
748
1120
  } catch (error) {
749
1121
  console.error(
750
- "INDEX.JS: Error in submitNewTransaction:",
751
- error.toString()
1122
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
1123
+ error
752
1124
  );
753
1125
  throw error;
754
1126
  }