@openzeppelin/miden-multisig-client 0.18.0-rc.2 → 0.18.0-rc.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.
Files changed (87) hide show
  1. package/README.md +46 -4
  2. package/dist/account/builder.d.ts +3 -4
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +6 -13
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/client.d.ts +11 -4
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +8 -3
  9. package/dist/client.js.map +1 -1
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +14 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/multisig.d.ts +40 -9
  19. package/dist/multisig.d.ts.map +1 -1
  20. package/dist/multisig.js +97 -108
  21. package/dist/multisig.js.map +1 -1
  22. package/dist/procedures.d.ts +5 -5
  23. package/dist/procedures.js +5 -5
  24. package/dist/recovery/proposalNoteImport.d.ts +5 -6
  25. package/dist/recovery/proposalNoteImport.d.ts.map +1 -1
  26. package/dist/recovery/proposalNoteImport.js +12 -13
  27. package/dist/recovery/proposalNoteImport.js.map +1 -1
  28. package/dist/recovery/publicNoteBackfill.d.ts +2 -3
  29. package/dist/recovery/publicNoteBackfill.d.ts.map +1 -1
  30. package/dist/recovery/publicNoteBackfill.js +4 -5
  31. package/dist/recovery/publicNoteBackfill.js.map +1 -1
  32. package/dist/transaction/authArgs.d.ts +2 -3
  33. package/dist/transaction/authArgs.d.ts.map +1 -1
  34. package/dist/transaction/authArgs.js +7 -14
  35. package/dist/transaction/authArgs.js.map +1 -1
  36. package/dist/transaction/consumeNotes.d.ts +4 -12
  37. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  38. package/dist/transaction/consumeNotes.js +10 -4
  39. package/dist/transaction/consumeNotes.js.map +1 -1
  40. package/dist/transaction/noteAuthentication.d.ts +2 -3
  41. package/dist/transaction/noteAuthentication.d.ts.map +1 -1
  42. package/dist/transaction/noteAuthentication.js +11 -11
  43. package/dist/transaction/noteAuthentication.js.map +1 -1
  44. package/dist/transaction/options.d.ts +0 -7
  45. package/dist/transaction/options.d.ts.map +1 -1
  46. package/dist/transaction/p2id.d.ts +2 -3
  47. package/dist/transaction/p2id.d.ts.map +1 -1
  48. package/dist/transaction/p2id.js.map +1 -1
  49. package/dist/transaction/summary.d.ts +8 -14
  50. package/dist/transaction/summary.d.ts.map +1 -1
  51. package/dist/transaction/summary.js +64 -20
  52. package/dist/transaction/summary.js.map +1 -1
  53. package/dist/transaction/updateGuardian.d.ts +3 -7
  54. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  55. package/dist/transaction/updateGuardian.js +3 -4
  56. package/dist/transaction/updateGuardian.js.map +1 -1
  57. package/dist/transaction/updateProcedureThreshold.d.ts +3 -8
  58. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  59. package/dist/transaction/updateProcedureThreshold.js +3 -4
  60. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  61. package/dist/transaction/updateSigners.d.ts +3 -8
  62. package/dist/transaction/updateSigners.d.ts.map +1 -1
  63. package/dist/transaction/updateSigners.js +3 -4
  64. package/dist/transaction/updateSigners.js.map +1 -1
  65. package/package.json +4 -4
  66. package/src/account/builder.ts +6 -13
  67. package/src/client.ts +13 -10
  68. package/src/config.ts +14 -0
  69. package/src/index.ts +2 -3
  70. package/src/multisig.ts +125 -143
  71. package/src/procedures.ts +5 -5
  72. package/src/recovery/proposalNoteImport.ts +13 -15
  73. package/src/recovery/publicNoteBackfill.ts +6 -6
  74. package/src/transaction/authArgs.ts +15 -21
  75. package/src/transaction/consumeNotes.ts +6 -34
  76. package/src/transaction/noteAuthentication.ts +18 -13
  77. package/src/transaction/options.ts +0 -9
  78. package/src/transaction/p2id.ts +2 -3
  79. package/src/transaction/summary.ts +27 -69
  80. package/src/transaction/updateGuardian.ts +5 -23
  81. package/src/transaction/updateProcedureThreshold.ts +5 -25
  82. package/src/transaction/updateSigners.ts +5 -22
  83. package/dist/raw-client.d.ts +0 -14
  84. package/dist/raw-client.d.ts.map +0 -1
  85. package/dist/raw-client.js +0 -60
  86. package/dist/raw-client.js.map +0 -1
  87. package/src/raw-client.ts +0 -89
package/dist/multisig.js CHANGED
@@ -27,7 +27,7 @@ import { importNotesFromProposals as importNotesFromProposalsStandalone, throwIf
27
27
  import { backfillPublicNotesByTag as backfillPublicNotesByTagStandalone, } from './recovery/publicNoteBackfill.js';
28
28
  import { drainPrivateNoteBacklog } from './recovery/transportDrain.js';
29
29
  import { GUARDIAN_SWITCH_RECOVERY_OPTIONS, runNoteRecovery, } from './recovery/recoverNotes.js';
30
- import { getRawMidenClient, getTransactionProver, requireMidenRpcEndpoint, } from './raw-client.js';
30
+ import { requireMidenRpcEndpoint } from './config.js';
31
31
  import { resolveProverConfig, } from './prover/config.js';
32
32
  import { ProverWorkflow } from './prover/workflow.js';
33
33
  import { resolveRpcConfig, } from './rpc/config.js';
@@ -141,7 +141,6 @@ export class Multisig {
141
141
  guardian;
142
142
  signer;
143
143
  midenClient;
144
- rawClientPromise;
145
144
  proverWorkflow;
146
145
  rpcConfig;
147
146
  _accountId;
@@ -177,16 +176,12 @@ export class Multisig {
177
176
  this.midenClient = midenClient;
178
177
  this._accountId = accountId ?? (account ? accountIdToHex(account) : '');
179
178
  this.midenRpcEndpoint = requireMidenRpcEndpoint(midenRpcEndpoint);
180
- this.rawClientPromise = getRawMidenClient(midenClient, this.midenRpcEndpoint);
181
- this.proverWorkflow = new ProverWorkflow(this.midenClient, proverConfig ?? resolveProverConfig(undefined, getTransactionProver(midenClient)));
179
+ this.proverWorkflow = new ProverWorkflow(this.midenClient, proverConfig ?? resolveProverConfig(undefined, midenClient.defaultProver));
182
180
  this.rpcConfig = rpcConfig ?? resolveRpcConfig(undefined);
183
181
  }
184
182
  getMidenRpcEndpoint() {
185
183
  return this.midenRpcEndpoint;
186
184
  }
187
- async getRawClient() {
188
- return this.rawClientPromise;
189
- }
190
185
  proposalFactory() {
191
186
  return new ProposalFactory({
192
187
  accountId: this._accountId,
@@ -215,7 +210,7 @@ export class Multisig {
215
210
  return this.signer.commitment;
216
211
  }
217
212
  /**
218
- * Resolve the account from the web client's store, falling back to the
213
+ * Resolve the account from the Miden client's store, falling back to the
219
214
  * `account` snapshot when the store has no record.
220
215
  *
221
216
  * Transaction execution reads the store, and other flows (e.g. consume-notes
@@ -223,9 +218,10 @@ export class Multisig {
223
218
  * source from the store to see the same state execution will.
224
219
  */
225
220
  async getStoreAccount() {
226
- const webClient = await this.getRawClient();
227
- const stored = await retryRpcRead(() => webClient.getAccount(AccountId.fromHex(this._accountId)), this.rpcConfig);
228
- return stored ?? this.account;
221
+ return (await this.readStoreAccount()) ?? this.account;
222
+ }
223
+ readStoreAccount() {
224
+ return retryRpcRead(() => this.midenClient.accounts.get(this._accountId), this.rpcConfig);
229
225
  }
230
226
  /**
231
227
  * Read the current ordered signer public-key commitments from account
@@ -375,9 +371,16 @@ export class Multisig {
375
371
  /**
376
372
  * Sync account state from GUARDIAN into the local Miden client store.
377
373
  *
378
- * If the GUARDIAN commitment differs from the local commitment (or the account
379
- * is missing locally) and the GUARDIAN state is safe to import, the local store
380
- * is overwritten with the GUARDIAN state. When the GUARDIAN is merely *behind*
374
+ * Starts with the canonical-nonce pre-check (OpenZeppelin/guardian#191):
375
+ * GUARDIAN reports the nonce and commitment of its canonical state, and
376
+ * when that nonce is not above the local account's — at a matching
377
+ * commitment when equal — nothing newer exists to pull, so the state
378
+ * fetch is skipped and the local account stands (`source: 'local'`).
379
+ *
380
+ * Otherwise the state is fetched (`source: 'guardian'`). If the GUARDIAN
381
+ * commitment differs from the local commitment (or the account is missing
382
+ * locally) and the GUARDIAN state is safe to import, the local store is
383
+ * overwritten with the GUARDIAN state. When the GUARDIAN is merely *behind*
381
384
  * local — e.g. the pushed execution delta has not been canonicalized yet
382
385
  * (see OpenZeppelin/guardian#316) — the local state is already ahead and
383
386
  * on-chain-verifiable, so it is kept as authoritative. Either way, config is
@@ -385,11 +388,18 @@ export class Multisig {
385
388
  * (e.g. the UI) observe the current state instead of a stale snapshot.
386
389
  */
387
390
  async syncState() {
391
+ const localAccount = await this.readStoreAccount();
392
+ if (localAccount) {
393
+ const head = await this.guardian.getCanonicalNonce(this._accountId);
394
+ const localNonce = localAccount.nonce().asInt();
395
+ const guardianNonce = BigInt(head.nonce);
396
+ if (this.guardianIsNotAhead(localAccount, localNonce, head.commitment, guardianNonce)) {
397
+ this.refreshConfigFromAccount(localAccount);
398
+ return { source: 'local', localNonce, guardianNonce };
399
+ }
400
+ }
388
401
  const state = await this.fetchState();
389
- const accountId = AccountId.fromHex(this._accountId);
390
- const webClient = await this.getRawClient();
391
- const localAccount = await retryRpcRead(() => webClient.getAccount(accountId), this.rpcConfig);
392
- let accountForConfigRefresh = localAccount ?? null;
402
+ let accountForConfigRefresh = localAccount;
393
403
  const guardianCommitment = normalizeHexWord(state.commitment);
394
404
  const localCommitment = localAccount
395
405
  ? normalizeHexWord(localAccount.to_commitment().toHex())
@@ -397,18 +407,33 @@ export class Multisig {
397
407
  if (!localAccount || localCommitment !== guardianCommitment) {
398
408
  const accountBytes = base64ToUint8Array(state.stateDataBase64);
399
409
  const incomingAccount = Account.deserialize(accountBytes);
400
- if (await this.isSafeToOverwriteLocalState(incomingAccount, localAccount)) {
401
- await webClient.newAccount(incomingAccount, true);
410
+ if (await this.isSafeToOverwriteLocalState(incomingAccount, localAccount ?? undefined)) {
411
+ await this.midenClient.accounts.insert({ account: incomingAccount, overwrite: true });
402
412
  accountForConfigRefresh = incomingAccount;
403
413
  }
404
414
  }
405
415
  this.refreshConfigFromAccount(accountForConfigRefresh);
406
- return state;
416
+ return { source: 'guardian', state };
417
+ }
418
+ /**
419
+ * The pre-check decision: GUARDIAN has nothing newer when its canonical
420
+ * nonce is below the local nonce, or equal to it at the same commitment.
421
+ * An equal nonce with a different commitment is divergence, which the
422
+ * full fetch reports (see `isSafeToOverwriteLocalState`).
423
+ */
424
+ guardianIsNotAhead(localAccount, localNonce, guardianCommitment, guardianNonce) {
425
+ if (guardianNonce < localNonce) {
426
+ return true;
427
+ }
428
+ if (guardianNonce > localNonce) {
429
+ return false;
430
+ }
431
+ return (normalizeHexWord(guardianCommitment) ===
432
+ normalizeHexWord(localAccount.to_commitment().toHex()));
407
433
  }
408
434
  async verifyStateCommitment() {
409
435
  const accountId = AccountId.fromHex(this._accountId);
410
- const webClient = await this.getRawClient();
411
- const localAccount = await retryRpcRead(() => webClient.getAccount(accountId), this.rpcConfig);
436
+ const localAccount = await this.readStoreAccount();
412
437
  if (!localAccount) {
413
438
  throw new Error(`Local account state not found for account ${this._accountId}. Sync the account before verifying.`);
414
439
  }
@@ -464,11 +489,10 @@ export class Multisig {
464
489
  * fail.
465
490
  */
466
491
  async syncNetworkOnly() {
467
- const webClient = await this.getRawClient();
468
- await retryRpcRead(() => webClient.syncState(), this.rpcConfig);
469
- const account = await retryRpcRead(() => webClient.getAccount(AccountId.fromHex(this._accountId)), this.rpcConfig);
470
- this.refreshConfigFromAccount(account ?? null);
471
- return account ?? null;
492
+ await this.syncChain();
493
+ const account = await this.readStoreAccount();
494
+ this.refreshConfigFromAccount(account);
495
+ return account;
472
496
  }
473
497
  refreshConfigFromAccount(account) {
474
498
  if (!account) {
@@ -736,7 +760,6 @@ export class Multisig {
736
760
  */
737
761
  async createAddSignerProposal(newCommitment, options = {}, ...legacyArgs) {
738
762
  const proposalNonce = resolveProposalNonce('createAddSignerProposal', options, legacyArgs);
739
- const webClient = await this.getRawClient();
740
763
  const targetThreshold = options.newThreshold ?? this.threshold;
741
764
  const targetSignerCommitments = [...this.signerCommitments, newCommitment];
742
765
  // What `update_signers_and_threshold` rejects on-chain, and what the auth
@@ -748,8 +771,8 @@ export class Multisig {
748
771
  guardianCommitment: this.guardianCommitment,
749
772
  });
750
773
  this.warnOnOverrideDilution(targetSignerCommitments.length);
751
- const { request, salt } = await buildUpdateSignersTransactionRequest(webClient, targetThreshold, targetSignerCommitments, this.proposalRequestOptions(options));
752
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
774
+ const { request, salt } = await buildUpdateSignersTransactionRequest(this.midenClient, targetThreshold, targetSignerCommitments, this.proposalRequestOptions(options));
775
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
753
776
  const chainAnchor = chainAnchorToBase64(anchor);
754
777
  anchor.free();
755
778
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -773,7 +796,6 @@ export class Multisig {
773
796
  */
774
797
  async createRemoveSignerProposal(signerToRemove, options = {}, ...legacyArgs) {
775
798
  const proposalNonce = resolveProposalNonce('createRemoveSignerProposal', options, legacyArgs);
776
- const webClient = await this.getRawClient();
777
799
  const normalizedRemove = signerToRemove.toLowerCase();
778
800
  const targetSignerCommitments = this.signerCommitments.filter((c) => c.toLowerCase() !== normalizedRemove);
779
801
  if (targetSignerCommitments.length === this.signerCommitments.length) {
@@ -786,8 +808,8 @@ export class Multisig {
786
808
  if (targetThreshold < 1 || targetThreshold > targetSignerCommitments.length) {
787
809
  throw new Error(`Invalid threshold ${targetThreshold}. Must be between 1 and ${targetSignerCommitments.length}`);
788
810
  }
789
- const { request, salt } = await buildUpdateSignersTransactionRequest(webClient, targetThreshold, targetSignerCommitments, this.proposalRequestOptions(options));
790
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
811
+ const { request, salt } = await buildUpdateSignersTransactionRequest(this.midenClient, targetThreshold, targetSignerCommitments, this.proposalRequestOptions(options));
812
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
791
813
  const chainAnchor = chainAnchorToBase64(anchor);
792
814
  anchor.free();
793
815
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -810,15 +832,14 @@ export class Multisig {
810
832
  */
811
833
  async createChangeThresholdProposal(newThreshold, options = {}) {
812
834
  const proposalNonce = resolveProposalNonce('createChangeThresholdProposal', options);
813
- const webClient = await this.getRawClient();
814
835
  if (newThreshold < 1 || newThreshold > this.signerCommitments.length) {
815
836
  throw new Error(`Invalid threshold ${newThreshold}. Must be between 1 and ${this.signerCommitments.length}`);
816
837
  }
817
838
  if (newThreshold === this.threshold) {
818
839
  throw new Error('New threshold is the same as current threshold');
819
840
  }
820
- const { request, salt } = await buildUpdateSignersTransactionRequest(webClient, newThreshold, this.signerCommitments, this.proposalRequestOptions(options));
821
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
841
+ const { request, salt } = await buildUpdateSignersTransactionRequest(this.midenClient, newThreshold, this.signerCommitments, this.proposalRequestOptions(options));
842
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
822
843
  const chainAnchor = chainAnchorToBase64(anchor);
823
844
  anchor.free();
824
845
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -835,7 +856,6 @@ export class Multisig {
835
856
  }
836
857
  async createUpdateProcedureThresholdProposal(targetProcedure, targetThreshold, options = {}) {
837
858
  const proposalNonce = resolveProposalNonce('createUpdateProcedureThresholdProposal', options);
838
- const webClient = await this.getRawClient();
839
859
  if (targetThreshold < 0 || targetThreshold > this.signerCommitments.length) {
840
860
  throw new Error(`Invalid threshold ${targetThreshold}. Must be between 0 and ${this.signerCommitments.length}`);
841
861
  }
@@ -846,8 +866,8 @@ export class Multisig {
846
866
  if (currentOverride !== undefined && currentOverride === targetThreshold) {
847
867
  throw new Error(`Procedure ${targetProcedure} already has threshold override ${targetThreshold}`);
848
868
  }
849
- const { request, salt } = await buildUpdateProcedureThresholdTransactionRequest(webClient, targetProcedure, targetThreshold, this.proposalRequestOptions(options));
850
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
869
+ const { request, salt } = await buildUpdateProcedureThresholdTransactionRequest(this.midenClient, targetProcedure, targetThreshold, this.proposalRequestOptions(options));
870
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
851
871
  const chainAnchor = chainAnchorToBase64(anchor);
852
872
  anchor.free();
853
873
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -887,7 +907,6 @@ export class Multisig {
887
907
  * proposals for the same operation can never drift apart.
888
908
  */
889
909
  async buildSwitchGuardianSummary(newGuardianEndpoint, newGuardianPubkey, approvalExpirationDelta) {
890
- const webClient = await this.getRawClient();
891
910
  // What `auth_tx_guarded_multisig` asserts after a guardian rotation, checked
892
911
  // before any signature is collected.
893
912
  validateMultisigConfig({
@@ -896,12 +915,12 @@ export class Multisig {
896
915
  guardianCommitment: newGuardianPubkey,
897
916
  });
898
917
  await this.verifyGuardianEndpointCommitment(newGuardianEndpoint, newGuardianPubkey);
899
- const { request, salt } = await buildUpdateGuardianTransactionRequest(webClient, newGuardianPubkey, {
918
+ const { request, salt } = await buildUpdateGuardianTransactionRequest(this.midenClient, newGuardianPubkey, {
900
919
  accountId: this._accountId,
901
920
  approvalExpirationDelta,
902
921
  signatureScheme: this.signer.scheme,
903
922
  });
904
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
923
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
905
924
  const chainAnchor = chainAnchorToBase64(anchor);
906
925
  anchor.free();
907
926
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -973,15 +992,13 @@ export class Multisig {
973
992
  */
974
993
  async createConsumeNotesProposal(noteIds, options = {}) {
975
994
  const proposalNonce = resolveProposalNonce('createConsumeNotesProposal', options);
976
- const webClient = await this.getRawClient();
977
995
  if (noteIds.length === 0) {
978
996
  throw new Error('At least one note ID is required');
979
997
  }
980
998
  // Fetch notes locally (proposer has them per FR-012); embed for v2 verification.
981
- const rawClient = await getRawMidenClient(webClient);
982
999
  const fetchedNotes = [];
983
1000
  for (const noteIdHex of noteIds) {
984
- const inputNoteRecord = await rawClient.getInputNote(noteIdHex);
1001
+ const inputNoteRecord = await this.midenClient.notes.get(noteIdHex);
985
1002
  if (!inputNoteRecord) {
986
1003
  throw new LegacyConsumeNotesNoteMissingError(noteIdHex);
987
1004
  }
@@ -992,8 +1009,8 @@ export class Multisig {
992
1009
  // the notes are authenticated here first, before the anchor is captured.
993
1010
  await this.ensureNotesAuthenticated(fetchedNotes);
994
1011
  const embeddedNotes = fetchedNotes.map((n) => noteToBase64(n));
995
- const { request, salt } = await buildConsumeNotesTransactionRequestFromNotes(webClient, fetchedNotes, this.proposalRequestOptions(options));
996
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1012
+ const { request, salt } = await buildConsumeNotesTransactionRequestFromNotes(this.midenClient, fetchedNotes, this.proposalRequestOptions(options));
1013
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
997
1014
  const chainAnchor = chainAnchorToBase64(anchor);
998
1015
  anchor.free();
999
1016
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1030,15 +1047,14 @@ export class Multisig {
1030
1047
  */
1031
1048
  async createP2idProposal(recipientId, faucetId, amount, options = {}, ...legacyArgs) {
1032
1049
  const proposalNonce = resolveProposalNonce('createP2idProposal', options, legacyArgs);
1033
- const webClient = await this.getRawClient();
1034
1050
  if (amount <= 0n) {
1035
1051
  throw new Error('Amount must be greater than 0');
1036
1052
  }
1037
1053
  // Forward everything but the nonce, so a note option added to
1038
1054
  // CreateP2idProposalOptions can't be silently dropped before the builder.
1039
1055
  const { nonce: _nonce, ...noteOptions } = options;
1040
- const { request, salt } = await buildP2idTransactionRequest(webClient, this._accountId, recipientId, faucetId, amount, noteOptions);
1041
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1056
+ const { request, salt } = await buildP2idTransactionRequest(this.midenClient, this._accountId, recipientId, faucetId, amount, noteOptions);
1057
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1042
1058
  const chainAnchor = chainAnchorToBase64(anchor);
1043
1059
  anchor.free();
1044
1060
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1062,41 +1078,24 @@ export class Multisig {
1062
1078
  /**
1063
1079
  * Get notes that can be consumed by this multisig account.
1064
1080
  *
1065
- * Returns a list of notes that are committed on-chain and can be consumed
1066
- * immediately by the multisig account.
1081
+ * Returns the notes the Miden client's last sync screened as consumable by
1082
+ * this account right now; block-locked notes are left out.
1067
1083
  */
1068
1084
  async getConsumableNotes() {
1069
- const accountId = AccountId.fromHex(this._accountId);
1070
- const webClient = await this.getRawClient();
1071
- // Get consumable notes for this account
1072
- const consumableRecords = await webClient.getConsumableNotes(accountId);
1073
- // Convert to our simplified ConsumableNote type
1085
+ const records = await this.midenClient.notes.listAvailable({ account: this._accountId });
1074
1086
  const notes = [];
1075
- for (const record of consumableRecords) {
1076
- const inputNote = record.inputNoteRecord();
1077
- const consumability = record.noteConsumability();
1078
- // Only include notes that can be consumed now (consumableAfterBlock is undefined/null)
1079
- const canConsumeNow = consumability.some((c) => c.accountId().toString().toLowerCase() === this._accountId.toLowerCase() &&
1080
- c.consumptionStatus().consumableAfterBlock() === undefined);
1081
- if (canConsumeNow) {
1082
- // Miden 0.15: InputNoteRecord.id() is `NoteId | undefined`; skip id-less records.
1083
- const id = inputNote.id();
1084
- if (id === undefined) {
1085
- continue;
1086
- }
1087
- const noteId = id.toString();
1088
- const details = inputNote.details();
1089
- const fungibleAssets = details.assets().fungibleAssets();
1090
- // Extract assets
1091
- const assets = [];
1092
- for (const asset of fungibleAssets) {
1093
- assets.push({
1094
- faucetId: asset.faucetId().toString(),
1095
- amount: asset.amount(),
1096
- });
1097
- }
1098
- notes.push({ id: noteId, assets });
1087
+ for (const inputNote of records) {
1088
+ // Miden 0.15: InputNoteRecord.id() is `NoteId | undefined`; skip id-less records.
1089
+ const id = inputNote.id();
1090
+ if (id === undefined) {
1091
+ continue;
1099
1092
  }
1093
+ const assets = inputNote
1094
+ .details()
1095
+ .assets()
1096
+ .fungibleAssets()
1097
+ .map((asset) => ({ faucetId: asset.faucetId().toString(), amount: asset.amount() }));
1098
+ notes.push({ id: id.toString(), assets });
1100
1099
  }
1101
1100
  return notes;
1102
1101
  }
@@ -1118,11 +1117,10 @@ export class Multisig {
1118
1117
  * @returns Serialized note file bytes
1119
1118
  */
1120
1119
  async exportNoteToBytes(noteId) {
1121
- const webClient = await this.getRawClient();
1122
1120
  const trimmedNoteId = noteId.trim();
1123
1121
  let record;
1124
1122
  try {
1125
- record = await webClient.getOutputNote(trimmedNoteId);
1123
+ [record] = await this.midenClient.notes.listSent({ ids: [trimmedNoteId] });
1126
1124
  }
1127
1125
  catch (err) {
1128
1126
  const detail = err instanceof Error ? err.message : String(err);
@@ -1134,7 +1132,7 @@ export class Multisig {
1134
1132
  const format = record.inclusionProof()
1135
1133
  ? NoteExportFormat.Full
1136
1134
  : NoteExportFormat.Details;
1137
- const noteFile = await webClient.exportNoteFile(trimmedNoteId, format);
1135
+ const noteFile = await this.midenClient.notes.export(trimmedNoteId, { format });
1138
1136
  return noteFile.serialize();
1139
1137
  }
1140
1138
  /**
@@ -1178,7 +1176,6 @@ export class Multisig {
1178
1176
  * commitment for a details-only file
1179
1177
  */
1180
1178
  async importNoteFromBytes(noteBytes) {
1181
- const webClient = await this.getRawClient();
1182
1179
  let noteFile;
1183
1180
  try {
1184
1181
  noteFile = NoteFile.deserialize(noteBytes);
@@ -1187,7 +1184,7 @@ export class Multisig {
1187
1184
  const detail = err instanceof Error ? err.message : String(err);
1188
1185
  throw new Error(`failed to decode note file: ${detail}`);
1189
1186
  }
1190
- return webClient.importNoteFile(noteFile);
1187
+ return this.midenClient.notes.import(noteFile);
1191
1188
  }
1192
1189
  /**
1193
1190
  * Import a note file received out-of-band from a browser
@@ -1600,8 +1597,7 @@ export class Multisig {
1600
1597
  * syncing first when this client is still below the block the request binds.
1601
1598
  */
1602
1599
  async submitAtTip(request) {
1603
- const webClient = await this.getRawClient();
1604
- await prepareTipExecution(webClient, request, () => retryRpcRead(() => webClient.syncState(), this.rpcConfig));
1600
+ await prepareTipExecution(this.midenClient, request, () => this.syncChain());
1605
1601
  await this.proverWorkflow.submit(AccountId.fromHex(this._accountId), request);
1606
1602
  }
1607
1603
  /**
@@ -1610,13 +1606,11 @@ export class Multisig {
1610
1606
  * store's sync height, which a node prunes about 50 blocks later.
1611
1607
  */
1612
1608
  async syncChain() {
1613
- const webClient = await this.getRawClient();
1614
- await retryRpcRead(() => webClient.syncState(), this.rpcConfig);
1609
+ await retryRpcRead(() => this.midenClient.syncChain(), this.rpcConfig);
1615
1610
  }
1616
1611
  /** {@link syncToBoundBlock} with this client's RPC retry policy. */
1617
1612
  async syncToBoundBlock(boundBlockNum) {
1618
- const webClient = await this.getRawClient();
1619
- await syncToBoundBlock(webClient, boundBlockNum, () => retryRpcRead(() => webClient.syncState(), this.rpcConfig));
1613
+ await syncToBoundBlock(this.midenClient, boundBlockNum, () => this.syncChain());
1620
1614
  }
1621
1615
  /**
1622
1616
  * Create a proposal from a producer-built transaction the SDK does not model.
@@ -1637,9 +1631,8 @@ export class Multisig {
1637
1631
  if (BUILTIN_PROPOSAL_TYPES.has(label)) {
1638
1632
  throw new Error(`'${label}' is a built-in proposal type; use the typed proposal API instead`);
1639
1633
  }
1640
- const webClient = await this.getRawClient();
1641
1634
  const request = deserializeTransactionRequest(transactionRequestBytes);
1642
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1635
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1643
1636
  const chainAnchor = chainAnchorToBase64(anchor);
1644
1637
  anchor.free();
1645
1638
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1695,8 +1688,7 @@ export class Multisig {
1695
1688
  finally {
1696
1689
  anchor.free();
1697
1690
  }
1698
- const webClient = await this.getRawClient();
1699
- const derived = await executeForSummaryAtTip(webClient, this._accountId, bindingRequest);
1691
+ const derived = await executeForSummaryAtTip(this.midenClient, this._accountId, bindingRequest);
1700
1692
  const derivedCommitmentHex = normalizeHexWord(derived.toCommitment().toHex());
1701
1693
  if (derivedCommitmentHex !== signedCommitmentHex) {
1702
1694
  throw new Error(`Custom proposal binding mismatch: expected ${signedCommitmentHex}, got ${derivedCommitmentHex}`);
@@ -2124,7 +2116,6 @@ export class Multisig {
2124
2116
  // to the block the summary binds; a cosigner that has only just loaded
2125
2117
  // the account has not.
2126
2118
  await this.syncToBoundBlock(binding.boundBlockNum);
2127
- const webClient = await this.getRawClient();
2128
2119
  // A consume-notes summary commits to *authenticated* consumption (see
2129
2120
  // ensureNotesAuthenticated), which miden-client decides from this store
2130
2121
  // alone. Put the store in that mode before the rebuild, or a cosigner
@@ -2134,7 +2125,7 @@ export class Multisig {
2134
2125
  await this.ensureNotesAuthenticated(decodeEmbeddedConsumeNotes(proposal.metadata));
2135
2126
  }
2136
2127
  const request = await this.buildTransactionRequestFromMetadata(proposal.metadata, binding);
2137
- const reconstructed = await executeForSummaryAtTip(webClient, this._accountId, request);
2128
+ const reconstructed = await executeForSummaryAtTip(this.midenClient, this._accountId, request);
2138
2129
  const reconstructedCommitment = normalizeHexWord(reconstructed.toCommitment().toHex());
2139
2130
  if (reconstructedCommitment !== txSummaryCommitment) {
2140
2131
  throw new Error(`Invalid proposal: metadata does not match tx_summary for ${proposal.id}`);
@@ -2209,8 +2200,7 @@ export class Multisig {
2209
2200
  if (expirationBlockNum === undefined) {
2210
2201
  return;
2211
2202
  }
2212
- const webClient = await this.getRawClient();
2213
- const syncHeight = await webClient.getSyncHeight();
2203
+ const syncHeight = await this.midenClient.getSyncHeight();
2214
2204
  if (syncHeight >= expirationBlockNum) {
2215
2205
  throw new Error(`Proposal ${proposalId} approval expired at block ${expirationBlockNum}; the chain is at ` +
2216
2206
  `block ${syncHeight}, so the collected signatures no longer authorize it`);
@@ -2239,20 +2229,19 @@ export class Multisig {
2239
2229
  }
2240
2230
  }
2241
2231
  async buildTransactionRequestWithOptions(metadata, requestOptions) {
2242
- const webClient = await this.getRawClient();
2243
2232
  switch (metadata.proposalType) {
2244
2233
  case 'add_signer':
2245
2234
  case 'remove_signer':
2246
2235
  case 'change_threshold': {
2247
- const { request } = await buildUpdateSignersTransactionRequest(webClient, metadata.targetThreshold, metadata.targetSignerCommitments, requestOptions);
2236
+ const { request } = await buildUpdateSignersTransactionRequest(this.midenClient, metadata.targetThreshold, metadata.targetSignerCommitments, requestOptions);
2248
2237
  return request;
2249
2238
  }
2250
2239
  case 'switch_guardian': {
2251
- const { request } = await buildUpdateGuardianTransactionRequest(webClient, metadata.newGuardianPubkey, requestOptions);
2240
+ const { request } = await buildUpdateGuardianTransactionRequest(this.midenClient, metadata.newGuardianPubkey, requestOptions);
2252
2241
  return request;
2253
2242
  }
2254
2243
  case 'update_procedure_threshold': {
2255
- const { request } = await buildUpdateProcedureThresholdTransactionRequest(webClient, metadata.targetProcedure, metadata.targetThreshold, requestOptions);
2244
+ const { request } = await buildUpdateProcedureThresholdTransactionRequest(this.midenClient, metadata.targetProcedure, metadata.targetThreshold, requestOptions);
2256
2245
  return request;
2257
2246
  }
2258
2247
  case 'consume_notes': {
@@ -2260,7 +2249,7 @@ export class Multisig {
2260
2249
  const version = metadata.metadataVersion;
2261
2250
  if (version === CONSUME_NOTES_METADATA_VERSION_V2) {
2262
2251
  const decoded = decodeEmbeddedConsumeNotes(metadata);
2263
- const { request } = await buildConsumeNotesTransactionRequestFromNotes(webClient, decoded, requestOptions);
2252
+ const { request } = await buildConsumeNotesTransactionRequestFromNotes(this.midenClient, decoded, requestOptions);
2264
2253
  return request;
2265
2254
  }
2266
2255
  if (version === undefined || version === 1) {
@@ -2269,13 +2258,13 @@ export class Multisig {
2269
2258
  // operator which legacy shape was rejected.
2270
2259
  throw new UnsupportedMetadataVersionError(version);
2271
2260
  }
2272
- const { request } = await buildConsumeNotesTransactionRequest(webClient, metadata.noteIds, requestOptions);
2261
+ const { request } = await buildConsumeNotesTransactionRequest(this.midenClient, metadata.noteIds, requestOptions);
2273
2262
  return request;
2274
2263
  }
2275
2264
  throw new UnsupportedMetadataVersionError(version);
2276
2265
  }
2277
2266
  case 'p2id': {
2278
- const { request } = await buildP2idTransactionRequest(webClient, this._accountId, metadata.recipientId, metadata.faucetId, BigInt(metadata.amount), {
2267
+ const { request } = await buildP2idTransactionRequest(this.midenClient, this._accountId, metadata.recipientId, metadata.faucetId, BigInt(metadata.amount), {
2279
2268
  ...requestOptions,
2280
2269
  noteType: parseP2idNoteType(metadata.noteType),
2281
2270
  reclaimHeight: metadata.reclaimHeight,