@miden-sdk/miden-sdk 0.13.0-next.2 → 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,8 +1,9 @@
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, AuthRpoFalcon512MultisigConfig, 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, ProcedureThreshold, 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, createAuthRpoFalcon512Multisig, initSync } from './Cargo-c25ff7e6.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",
7
8
  EXECUTE_CALLBACK: "executeCallback",
8
9
  });
@@ -21,10 +22,223 @@ const MethodName = Object.freeze({
21
22
  PROVE_TRANSACTION: "proveTransaction",
22
23
  SUBMIT_NEW_TRANSACTION: "submitNewTransaction",
23
24
  SUBMIT_NEW_TRANSACTION_MOCK: "submitNewTransactionMock",
25
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER: "submitNewTransactionWithProver",
26
+ SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK: "submitNewTransactionWithProverMock",
24
27
  SYNC_STATE: "syncState",
25
28
  SYNC_STATE_MOCK: "syncStateMock",
26
29
  });
27
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
+
28
242
  const buildTypedArraysExport = (exportObject) => {
29
243
  return Object.entries(exportObject).reduce(
30
244
  (exports, [exportName, _export]) => {
@@ -37,6 +251,27 @@ const buildTypedArraysExport = (exportObject) => {
37
251
  );
38
252
  };
39
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
+
40
275
  const MidenArrays = {};
41
276
 
42
277
  let wasmModule = null;
@@ -103,25 +338,35 @@ class WebClient {
103
338
  *
104
339
  * @param {string | undefined} rpcUrl - RPC endpoint URL used by the client.
105
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.
106
342
  * @param {(pubKey: Uint8Array) => Promise<Uint8Array | null | undefined> | Uint8Array | null | undefined} [getKeyCb]
107
343
  * - Callback to retrieve the secret key bytes for a given public key. The `pubKey`
108
344
  * parameter is the serialized public key (from `PublicKey.serialize()`). Return the
109
345
  * corresponding secret key as a `Uint8Array`, or `null`/`undefined` if not found. The
110
346
  * return value may be provided synchronously or via a `Promise`.
111
- * @param {(pubKey: Uint8Array, secretKey: Uint8Array) => Promise<void> | void} [insertKeyCb]
347
+ * @param {(pubKey: Uint8Array, AuthSecretKey: Uint8Array) => Promise<void> | void} [insertKeyCb]
112
348
  * - Callback to persist a secret key. `pubKey` is the serialized public key, and
113
- * `secretKey` is the serialized secret key (from `SecretKey.serialize()`). May return
349
+ * `authSecretKey` is the serialized secret key (from `AuthSecretKey.serialize()`). May return
114
350
  * `void` or a `Promise<void>`.
115
- * @param {(pubKey: Uint8Array, signingInputs: Uint8Array) => Promise<Array<number | string>> | Array<number | string>} [signCb]
116
- * - 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
117
353
  * serialized public key, and `signingInputs` is a `Uint8Array` produced by
118
- * `SigningInputs.serialize()`. Must return an array of numeric values (numbers or numeric
119
- * 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`.
120
356
  */
121
- constructor(rpcUrl, noteTransportUrl, seed, getKeyCb, insertKeyCb, signCb) {
357
+ constructor(
358
+ rpcUrl,
359
+ noteTransportUrl,
360
+ seed,
361
+ storeName,
362
+ getKeyCb,
363
+ insertKeyCb,
364
+ signCb
365
+ ) {
122
366
  this.rpcUrl = rpcUrl;
123
367
  this.noteTransportUrl = noteTransportUrl;
124
368
  this.seed = seed;
369
+ this.storeName = storeName;
125
370
  this.getKeyCb = getKeyCb;
126
371
  this.insertKeyCb = insertKeyCb;
127
372
  this.signCb = signCb;
@@ -200,11 +445,13 @@ class WebClient {
200
445
  const { resolve, reject } = this.pendingRequests.get(requestId);
201
446
  this.pendingRequests.delete(requestId);
202
447
  if (error) {
448
+ const workerError =
449
+ error instanceof Error ? error : deserializeError(error);
203
450
  console.error(
204
451
  `WebClient: Error from worker in ${methodName}:`,
205
- error
452
+ workerError
206
453
  );
207
- reject(new Error(error));
454
+ reject(workerError);
208
455
  } else {
209
456
  resolve(result);
210
457
  }
@@ -212,19 +459,7 @@ class WebClient {
212
459
  });
213
460
 
214
461
  // Once the worker script has loaded, initialize the worker.
215
- this.loaded.then(() => {
216
- this.worker.postMessage({
217
- action: WorkerAction.INIT,
218
- args: [
219
- this.rpcUrl,
220
- this.noteTransportUrl,
221
- this.seed,
222
- !!this.getKeyCb,
223
- !!this.insertKeyCb,
224
- !!this.signCb,
225
- ],
226
- });
227
- });
462
+ this.loaded.then(() => this.initializeWorker());
228
463
  } else {
229
464
  console.log("WebClient: Web Workers are not available.");
230
465
  // Worker not available; set up fallback values.
@@ -239,6 +474,24 @@ class WebClient {
239
474
  this.wasmWebClientPromise = null;
240
475
  }
241
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
+
242
495
  async getWasmWebClient() {
243
496
  if (this.wasmWebClient) {
244
497
  return this.wasmWebClient;
@@ -261,15 +514,16 @@ class WebClient {
261
514
  * @param {string} rpcUrl - The RPC URL.
262
515
  * @param {string} noteTransportUrl - The note transport URL (optional).
263
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.
264
518
  * @returns {Promise<WebClient>} The fully initialized WebClient.
265
519
  */
266
- static async createClient(rpcUrl, noteTransportUrl, seed) {
520
+ static async createClient(rpcUrl, noteTransportUrl, seed, network) {
267
521
  // Construct the instance (synchronously).
268
- const instance = new WebClient(rpcUrl, noteTransportUrl, seed);
522
+ const instance = new WebClient(rpcUrl, noteTransportUrl, seed, network);
269
523
 
270
524
  // Wait for the underlying wasmWebClient to be initialized.
271
525
  const wasmWebClient = await instance.getWasmWebClient();
272
- await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed);
526
+ await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed, network);
273
527
 
274
528
  // Wait for the worker to be ready
275
529
  await instance.ready;
@@ -301,6 +555,7 @@ class WebClient {
301
555
  * @param {string} rpcUrl - The RPC URL.
302
556
  * @param {string | undefined} noteTransportUrl - The note transport URL (optional).
303
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.
304
559
  * @param {Function | undefined} getKeyCb - The get key callback.
305
560
  * @param {Function | undefined} insertKeyCb - The insert key callback.
306
561
  * @param {Function | undefined} signCb - The sign callback.
@@ -310,6 +565,7 @@ class WebClient {
310
565
  rpcUrl,
311
566
  noteTransportUrl,
312
567
  seed,
568
+ storeName,
313
569
  getKeyCb,
314
570
  insertKeyCb,
315
571
  signCb
@@ -319,19 +575,23 @@ class WebClient {
319
575
  rpcUrl,
320
576
  noteTransportUrl,
321
577
  seed,
578
+ storeName,
322
579
  getKeyCb,
323
580
  insertKeyCb,
324
581
  signCb
325
582
  );
583
+ // Wait for the underlying wasmWebClient to be initialized.
326
584
  const wasmWebClient = await instance.getWasmWebClient();
327
585
  await wasmWebClient.createClientWithExternalKeystore(
328
586
  rpcUrl,
329
587
  noteTransportUrl,
330
588
  seed,
589
+ storeName,
331
590
  getKeyCb,
332
591
  insertKeyCb,
333
592
  signCb
334
593
  );
594
+
335
595
  await instance.ready;
336
596
  // Return a proxy that forwards missing properties to wasmWebClient.
337
597
  return new Proxy(instance, {
@@ -400,7 +660,7 @@ class WebClient {
400
660
  );
401
661
  return wasm.Account.deserialize(new Uint8Array(serializedAccountBytes));
402
662
  } catch (error) {
403
- console.error("INDEX.JS: Error in newWallet:", error.toString());
663
+ console.error("INDEX.JS: Error in newWallet:", error);
404
664
  throw error;
405
665
  }
406
666
  }
@@ -440,7 +700,7 @@ class WebClient {
440
700
 
441
701
  return wasm.Account.deserialize(new Uint8Array(serializedAccountBytes));
442
702
  } catch (error) {
443
- console.error("INDEX.JS: Error in newFaucet:", error.toString());
703
+ console.error("INDEX.JS: Error in newFaucet:", error);
444
704
  throw error;
445
705
  }
446
706
  }
@@ -469,83 +729,69 @@ class WebClient {
469
729
 
470
730
  return transactionResult.id();
471
731
  } catch (error) {
472
- console.error(
473
- "INDEX.JS: Error in submitNewTransaction:",
474
- error.toString()
475
- );
732
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
476
733
  throw error;
477
734
  }
478
735
  }
479
736
 
480
- async executeTransaction(accountId, transactionRequest) {
737
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
481
738
  try {
482
739
  if (!this.worker) {
483
740
  const wasmWebClient = await this.getWasmWebClient();
484
- return await wasmWebClient.executeTransaction(
741
+ return await wasmWebClient.submitNewTransactionWithProver(
485
742
  accountId,
486
- transactionRequest
743
+ transactionRequest,
744
+ prover
487
745
  );
488
746
  }
489
747
 
490
748
  const wasm = await getWasmOrThrow();
491
749
  const serializedTransactionRequest = transactionRequest.serialize();
492
- const serializedResultBytes = await this.callMethodWithWorker(
493
- MethodName.EXECUTE_TRANSACTION,
750
+ const proverPayload = prover.serialize();
751
+ const result = await this.callMethodWithWorker(
752
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER,
494
753
  accountId.toString(),
495
- serializedTransactionRequest
754
+ serializedTransactionRequest,
755
+ proverPayload
496
756
  );
497
757
 
498
- return wasm.TransactionResult.deserialize(
499
- new Uint8Array(serializedResultBytes)
758
+ const transactionResult = wasm.TransactionResult.deserialize(
759
+ new Uint8Array(result.serializedTransactionResult)
500
760
  );
761
+
762
+ return transactionResult.id();
501
763
  } catch (error) {
502
- console.error("INDEX.JS: Error in executeTransaction:", error.toString());
764
+ console.error(
765
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
766
+ error
767
+ );
503
768
  throw error;
504
769
  }
505
770
  }
506
771
 
507
- async submitTransaction(transactionResult, prover) {
772
+ async executeTransaction(accountId, transactionRequest) {
508
773
  try {
509
774
  if (!this.worker) {
510
775
  const wasmWebClient = await this.getWasmWebClient();
511
- const proven = await wasmWebClient.proveTransaction(
512
- transactionResult,
513
- prover
514
- );
515
- const submissionHeight = await wasmWebClient.submitProvenTransaction(
516
- proven,
517
- transactionResult
518
- );
519
- return await wasmWebClient.applyTransaction(
520
- transactionResult,
521
- submissionHeight
776
+ return await wasmWebClient.executeTransaction(
777
+ accountId,
778
+ transactionRequest
522
779
  );
523
780
  }
524
781
 
525
782
  const wasm = await getWasmOrThrow();
526
- const serializedTransactionResult = transactionResult.serialize();
527
- const proverPayload = prover ? prover.serialize() : null;
528
-
529
- const { submissionHeight, serializedTransactionUpdate } =
530
- await this.callMethodWithWorker(
531
- MethodName.SUBMIT_TRANSACTION,
532
- serializedTransactionResult,
533
- proverPayload
534
- );
535
-
536
- if (this instanceof MockWebClient) {
537
- return wasm.TransactionStoreUpdate.deserialize(
538
- new Uint8Array(serializedTransactionUpdate)
539
- );
540
- }
783
+ const serializedTransactionRequest = transactionRequest.serialize();
784
+ const serializedResultBytes = await this.callMethodWithWorker(
785
+ MethodName.EXECUTE_TRANSACTION,
786
+ accountId.toString(),
787
+ serializedTransactionRequest
788
+ );
541
789
 
542
- const wasmWebClient = await this.getWasmWebClient();
543
- return await wasmWebClient.applyTransaction(
544
- transactionResult,
545
- submissionHeight
790
+ return wasm.TransactionResult.deserialize(
791
+ new Uint8Array(serializedResultBytes)
546
792
  );
547
793
  } catch (error) {
548
- console.error("INDEX.JS: Error in submitTransaction:", error.toString());
794
+ console.error("INDEX.JS: Error in executeTransaction:", error);
549
795
  throw error;
550
796
  }
551
797
  }
@@ -571,28 +817,73 @@ class WebClient {
571
817
  new Uint8Array(serializedProvenBytes)
572
818
  );
573
819
  } catch (error) {
574
- console.error("INDEX.JS: Error in proveTransaction:", error.toString());
820
+ console.error("INDEX.JS: Error in proveTransaction:", error);
575
821
  throw error;
576
822
  }
577
823
  }
578
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
+ */
579
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
+
580
852
  try {
581
- if (!this.worker) {
582
- const wasmWebClient = await this.getWasmWebClient();
583
- 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;
584
859
  }
585
860
 
586
- const wasm = await getWasmOrThrow();
587
- const serializedSyncSummaryBytes = await this.callMethodWithWorker(
588
- MethodName.SYNC_STATE
589
- );
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
+ }
590
876
 
591
- return wasm.SyncSummary.deserialize(
592
- new Uint8Array(serializedSyncSummaryBytes)
593
- );
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
+ }
594
885
  } catch (error) {
595
- console.error("INDEX.JS: Error in syncState:", error.toString());
886
+ console.error("INDEX.JS: Error in syncState:", error);
596
887
  throw error;
597
888
  }
598
889
  }
@@ -606,7 +897,14 @@ class WebClient {
606
897
 
607
898
  class MockWebClient extends WebClient {
608
899
  constructor(seed) {
609
- 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
+ });
610
908
  }
611
909
 
612
910
  /**
@@ -656,65 +954,103 @@ class MockWebClient extends WebClient {
656
954
  });
657
955
  }
658
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
+ */
659
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
+
660
979
  try {
661
- const wasmWebClient = await this.getWasmWebClient();
662
- if (!this.worker) {
663
- return await wasmWebClient.syncState();
980
+ const lockHandle = await acquireSyncLock(dbId, timeoutMs);
981
+
982
+ if (!lockHandle.acquired) {
983
+ return lockHandle.coalescedResult;
664
984
  }
665
985
 
666
- let serializedMockChain = wasmWebClient.serializeMockChain().buffer;
667
- let serializedMockNoteTransportNode =
668
- wasmWebClient.serializeMockNoteTransportNode().buffer;
986
+ try {
987
+ let result;
988
+ const wasmWebClient = await this.getWasmWebClient();
669
989
 
670
- 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;
671
996
 
672
- const serializedSyncSummaryBytes = await this.callMethodWithWorker(
673
- MethodName.SYNC_STATE_MOCK,
674
- serializedMockChain,
675
- serializedMockNoteTransportNode
676
- );
997
+ const wasm = await getWasmOrThrow();
677
998
 
678
- return wasm.SyncSummary.deserialize(
679
- new Uint8Array(serializedSyncSummaryBytes)
680
- );
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
+ }
681
1016
  } catch (error) {
682
- console.error("INDEX.JS: Error in syncState:", error.toString());
1017
+ console.error("INDEX.JS: Error in syncState:", error);
683
1018
  throw error;
684
1019
  }
685
1020
  }
686
1021
 
687
- async submitTransaction(transactionResult, prover) {
1022
+ async submitNewTransaction(accountId, transactionRequest) {
688
1023
  try {
689
1024
  if (!this.worker) {
690
- return await super.submitTransaction(transactionResult, prover);
1025
+ return await super.submitNewTransaction(accountId, transactionRequest);
691
1026
  }
692
1027
 
693
1028
  const wasmWebClient = await this.getWasmWebClient();
694
1029
  const wasm = await getWasmOrThrow();
695
- const serializedTransactionResult = transactionResult.serialize();
696
- const proverPayload = prover ? prover.serialize() : null;
1030
+ const serializedTransactionRequest = transactionRequest.serialize();
697
1031
  const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
698
1032
  const serializedMockNoteTransportNode =
699
1033
  wasmWebClient.serializeMockNoteTransportNode().buffer;
700
1034
 
701
1035
  const result = await this.callMethodWithWorker(
702
- MethodName.SUBMIT_TRANSACTION_MOCK,
703
- serializedTransactionResult,
704
- proverPayload,
1036
+ MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
1037
+ accountId.toString(),
1038
+ serializedTransactionRequest,
705
1039
  serializedMockChain,
706
1040
  serializedMockNoteTransportNode
707
1041
  );
1042
+
708
1043
  const newMockChain = new Uint8Array(result.serializedMockChain);
709
1044
  const newMockNoteTransportNode = result.serializedMockNoteTransportNode
710
1045
  ? new Uint8Array(result.serializedMockNoteTransportNode)
711
1046
  : undefined;
712
1047
 
1048
+ const transactionResult = wasm.TransactionResult.deserialize(
1049
+ new Uint8Array(result.serializedTransactionResult)
1050
+ );
1051
+
713
1052
  if (!(this instanceof MockWebClient)) {
714
- return await wasmWebClient.applyTransaction(
715
- transactionResult,
716
- result.submissionHeight
717
- );
1053
+ return transactionResult.id();
718
1054
  }
719
1055
 
720
1056
  this.wasmWebClient = new wasm.WebClient();
@@ -725,32 +1061,36 @@ class MockWebClient extends WebClient {
725
1061
  newMockNoteTransportNode
726
1062
  );
727
1063
 
728
- return wasm.TransactionStoreUpdate.deserialize(
729
- new Uint8Array(result.serializedTransactionUpdate)
730
- );
1064
+ return transactionResult.id();
731
1065
  } catch (error) {
732
- console.error("INDEX.JS: Error in submitTransaction:", error.toString());
1066
+ console.error("INDEX.JS: Error in submitNewTransaction:", error);
733
1067
  throw error;
734
1068
  }
735
1069
  }
736
1070
 
737
- async submitNewTransaction(accountId, transactionRequest) {
1071
+ async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
738
1072
  try {
739
1073
  if (!this.worker) {
740
- return await super.submitNewTransaction(accountId, transactionRequest);
1074
+ return await super.submitNewTransactionWithProver(
1075
+ accountId,
1076
+ transactionRequest,
1077
+ prover
1078
+ );
741
1079
  }
742
1080
 
743
1081
  const wasmWebClient = await this.getWasmWebClient();
744
1082
  const wasm = await getWasmOrThrow();
745
1083
  const serializedTransactionRequest = transactionRequest.serialize();
1084
+ const proverPayload = prover.serialize();
746
1085
  const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
747
1086
  const serializedMockNoteTransportNode =
748
1087
  wasmWebClient.serializeMockNoteTransportNode().buffer;
749
1088
 
750
1089
  const result = await this.callMethodWithWorker(
751
- MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
1090
+ MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK,
752
1091
  accountId.toString(),
753
1092
  serializedTransactionRequest,
1093
+ proverPayload,
754
1094
  serializedMockChain,
755
1095
  serializedMockNoteTransportNode
756
1096
  );
@@ -779,8 +1119,8 @@ class MockWebClient extends WebClient {
779
1119
  return transactionResult.id();
780
1120
  } catch (error) {
781
1121
  console.error(
782
- "INDEX.JS: Error in submitNewTransaction:",
783
- error.toString()
1122
+ "INDEX.JS: Error in submitNewTransactionWithProver:",
1123
+ error
784
1124
  );
785
1125
  throw error;
786
1126
  }