@openzeppelin/miden-multisig-client 0.13.3 → 0.14.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 (145) hide show
  1. package/README.md +10 -13
  2. package/dist/account/builder.d.ts +3 -3
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +10 -10
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/builder.test.js +16 -14
  7. package/dist/account/builder.test.js.map +1 -1
  8. package/dist/account/masm/account-components/auth.d.ts +4 -4
  9. package/dist/account/masm/account-components/auth.d.ts.map +1 -1
  10. package/dist/account/masm/account-components/auth.js +8 -12
  11. package/dist/account/masm/account-components/auth.js.map +1 -1
  12. package/dist/account/masm/auth.d.ts +4 -4
  13. package/dist/account/masm/auth.d.ts.map +1 -1
  14. package/dist/account/masm/auth.js +226 -124
  15. package/dist/account/masm/auth.js.map +1 -1
  16. package/dist/account/storage.d.ts.map +1 -1
  17. package/dist/account/storage.js +24 -6
  18. package/dist/account/storage.js.map +1 -1
  19. package/dist/account/storage.test.d.ts +2 -0
  20. package/dist/account/storage.test.d.ts.map +1 -0
  21. package/dist/account/storage.test.js +73 -0
  22. package/dist/account/storage.test.js.map +1 -0
  23. package/dist/client.d.ts +9 -9
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +24 -14
  26. package/dist/client.js.map +1 -1
  27. package/dist/client.test.js +45 -8
  28. package/dist/client.test.js.map +1 -1
  29. package/dist/index.d.ts +6 -12
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +6 -12
  32. package/dist/index.js.map +1 -1
  33. package/dist/multisig/proposal/execution.d.ts +3 -2
  34. package/dist/multisig/proposal/execution.d.ts.map +1 -1
  35. package/dist/multisig/proposal/execution.js +8 -12
  36. package/dist/multisig/proposal/execution.js.map +1 -1
  37. package/dist/multisig.d.ts +10 -4
  38. package/dist/multisig.d.ts.map +1 -1
  39. package/dist/multisig.js +97 -71
  40. package/dist/multisig.js.map +1 -1
  41. package/dist/multisig.test.js +481 -29
  42. package/dist/multisig.test.js.map +1 -1
  43. package/dist/procedures.d.ts +8 -8
  44. package/dist/procedures.js +8 -8
  45. package/dist/raw-client.d.ts +13 -0
  46. package/dist/raw-client.d.ts.map +1 -0
  47. package/dist/raw-client.js +42 -0
  48. package/dist/raw-client.js.map +1 -0
  49. package/dist/raw-client.test.d.ts +2 -0
  50. package/dist/raw-client.test.d.ts.map +1 -0
  51. package/dist/raw-client.test.js +89 -0
  52. package/dist/raw-client.test.js.map +1 -0
  53. package/dist/signers/ecdsa.d.ts +3 -1
  54. package/dist/signers/ecdsa.d.ts.map +1 -1
  55. package/dist/signers/ecdsa.js +15 -1
  56. package/dist/signers/ecdsa.js.map +1 -1
  57. package/dist/signers/ecdsa.test.js +27 -4
  58. package/dist/signers/ecdsa.test.js.map +1 -1
  59. package/dist/signers/falcon.d.ts +3 -1
  60. package/dist/signers/falcon.d.ts.map +1 -1
  61. package/dist/signers/falcon.js +15 -1
  62. package/dist/signers/falcon.js.map +1 -1
  63. package/dist/signers/falcon.test.js +39 -3
  64. package/dist/signers/falcon.test.js.map +1 -1
  65. package/dist/transaction/consumeNotes.d.ts +2 -2
  66. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  67. package/dist/transaction/consumeNotes.js +5 -4
  68. package/dist/transaction/consumeNotes.js.map +1 -1
  69. package/dist/transaction/options.d.ts +1 -0
  70. package/dist/transaction/options.d.ts.map +1 -1
  71. package/dist/transaction/p2id.d.ts.map +1 -1
  72. package/dist/transaction/p2id.js +7 -10
  73. package/dist/transaction/p2id.js.map +1 -1
  74. package/dist/transaction/p2id.test.js +19 -9
  75. package/dist/transaction/p2id.test.js.map +1 -1
  76. package/dist/transaction/summary.d.ts +2 -2
  77. package/dist/transaction/summary.d.ts.map +1 -1
  78. package/dist/transaction/summary.js +4 -2
  79. package/dist/transaction/summary.js.map +1 -1
  80. package/dist/transaction/updateGuardian.d.ts +2 -2
  81. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  82. package/dist/transaction/updateGuardian.js +8 -11
  83. package/dist/transaction/updateGuardian.js.map +1 -1
  84. package/dist/transaction/updateProcedureThreshold.d.ts +2 -2
  85. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  86. package/dist/transaction/updateProcedureThreshold.js +8 -10
  87. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  88. package/dist/transaction/updateSigners.d.ts +2 -2
  89. package/dist/transaction/updateSigners.d.ts.map +1 -1
  90. package/dist/transaction/updateSigners.js +8 -10
  91. package/dist/transaction/updateSigners.js.map +1 -1
  92. package/dist/transaction.test.js +6 -6
  93. package/dist/transaction.test.js.map +1 -1
  94. package/dist/utils/key.d.ts.map +1 -1
  95. package/dist/utils/key.js +3 -3
  96. package/dist/utils/key.js.map +1 -1
  97. package/dist/utils/key.test.js +4 -2
  98. package/dist/utils/key.test.js.map +1 -1
  99. package/dist/utils/signature.d.ts +2 -0
  100. package/dist/utils/signature.d.ts.map +1 -1
  101. package/dist/utils/signature.js +10 -5
  102. package/dist/utils/signature.js.map +1 -1
  103. package/dist/utils/signature.test.js +9 -9
  104. package/dist/utils/signature.test.js.map +1 -1
  105. package/masm/account_components/auth/multisig.masm +2 -3
  106. package/masm/account_components/auth/multisig_ecdsa.masm +2 -3
  107. package/masm/account_components/auth/multisig_guardian.masm +2 -3
  108. package/masm/account_components/auth/multisig_guardian_ecdsa.masm +2 -3
  109. package/masm/auth/guardian.masm +30 -8
  110. package/masm/auth/guardian_ecdsa.masm +24 -7
  111. package/masm/auth/multisig.masm +86 -55
  112. package/masm/auth/multisig_ecdsa.masm +86 -54
  113. package/package.json +3 -3
  114. package/src/account/builder.test.ts +16 -14
  115. package/src/account/builder.ts +13 -12
  116. package/src/account/masm/account-components/auth.ts +8 -12
  117. package/src/account/masm/auth.ts +226 -124
  118. package/src/account/storage.test.ts +90 -0
  119. package/src/account/storage.ts +27 -6
  120. package/src/client.test.ts +52 -8
  121. package/src/client.ts +39 -16
  122. package/src/index.ts +6 -12
  123. package/src/multisig/proposal/execution.ts +18 -14
  124. package/src/multisig.test.ts +572 -29
  125. package/src/multisig.ts +121 -83
  126. package/src/procedures.ts +8 -8
  127. package/src/raw-client.test.ts +133 -0
  128. package/src/raw-client.ts +75 -0
  129. package/src/signers/ecdsa.test.ts +35 -4
  130. package/src/signers/ecdsa.ts +18 -2
  131. package/src/signers/falcon.test.ts +50 -3
  132. package/src/signers/falcon.ts +18 -2
  133. package/src/transaction/consumeNotes.ts +16 -5
  134. package/src/transaction/options.ts +1 -0
  135. package/src/transaction/p2id.test.ts +19 -9
  136. package/src/transaction/p2id.ts +8 -13
  137. package/src/transaction/summary.ts +11 -4
  138. package/src/transaction/updateGuardian.ts +22 -14
  139. package/src/transaction/updateProcedureThreshold.ts +20 -15
  140. package/src/transaction/updateSigners.ts +22 -14
  141. package/src/transaction.test.ts +6 -6
  142. package/src/utils/key.test.ts +4 -2
  143. package/src/utils/key.ts +7 -3
  144. package/src/utils/signature.test.ts +10 -10
  145. package/src/utils/signature.ts +12 -5
package/src/multisig.ts CHANGED
@@ -16,7 +16,12 @@ import type {
16
16
  ProposalType,
17
17
  } from './types.js';
18
18
  import type { ProcedureName } from './procedures.js';
19
- import type { WebClient, TransactionRequest } from '@miden-sdk/miden-sdk';
19
+ import type {
20
+ MidenClient,
21
+ TransactionProver,
22
+ TransactionRequest,
23
+ WasmWebClient,
24
+ } from '@miden-sdk/miden-sdk';
20
25
  import {
21
26
  Account,
22
27
  AccountId,
@@ -53,6 +58,7 @@ import { AccountInspector } from './inspector.js';
53
58
  import { ProposalFactory } from './proposal/factory.js';
54
59
  import { ProposalMetadataCodec } from './proposal/metadata.js';
55
60
  import { ProposalSignatures } from './proposal/signatures.js';
61
+ import { getRawMidenClient, getTransactionProver } from './raw-client.js';
56
62
 
57
63
  /**
58
64
  * Result of fetching account state from GUARDIAN.
@@ -87,7 +93,9 @@ export class Multisig {
87
93
 
88
94
  private guardian: GuardianHttpClient;
89
95
  private readonly signer: Signer;
90
- private readonly webClient: WebClient;
96
+ private readonly midenClient: MidenClient;
97
+ private readonly rawClientPromise: Promise<WasmWebClient>;
98
+ private readonly transactionProver: TransactionProver | null;
91
99
  private readonly _accountId: string;
92
100
  private readonly midenRpcEndpoint?: string;
93
101
  private proposals: Map<string, Proposal> = new Map();
@@ -97,7 +105,7 @@ export class Multisig {
97
105
  config: MultisigConfig,
98
106
  guardian: GuardianHttpClient,
99
107
  signer: Signer,
100
- webClient: WebClient,
108
+ midenClient: MidenClient,
101
109
  accountId?: string,
102
110
  midenRpcEndpoint?: string
103
111
  ) {
@@ -111,9 +119,11 @@ export class Multisig {
111
119
  );
112
120
  this.guardian = guardian;
113
121
  this.signer = signer;
114
- this.webClient = webClient;
122
+ this.midenClient = midenClient;
115
123
  this._accountId = accountId ?? (account ? accountIdToHex(account) : '');
116
124
  this.midenRpcEndpoint = midenRpcEndpoint;
125
+ this.rawClientPromise = getRawMidenClient(midenClient, midenRpcEndpoint);
126
+ this.transactionProver = getTransactionProver(midenClient);
117
127
  }
118
128
 
119
129
  private getMidenRpcEndpoint(): string {
@@ -123,6 +133,10 @@ export class Multisig {
123
133
  return this.midenRpcEndpoint;
124
134
  }
125
135
 
136
+ private async getRawClient(): Promise<WasmWebClient> {
137
+ return this.rawClientPromise;
138
+ }
139
+
126
140
  private proposalFactory(): ProposalFactory {
127
141
  return new ProposalFactory({
128
142
  accountId: this._accountId,
@@ -228,7 +242,7 @@ export class Multisig {
228
242
  }
229
243
 
230
244
  /**
231
- * Sync account state from GUARDIAN into the local WebClient store.
245
+ * Sync account state from GUARDIAN into the local Miden client store.
232
246
  *
233
247
  * If the GUARDIAN commitment differs from the local commitment (or the account
234
248
  * is missing locally), the local store is overwritten with the GUARDIAN state.
@@ -236,19 +250,20 @@ export class Multisig {
236
250
  async syncState(): Promise<AccountState> {
237
251
  const state = await this.fetchState();
238
252
  const accountId = AccountId.fromHex(this._accountId);
239
- const localAccount = await this.webClient.getAccount(accountId);
253
+ const webClient = await this.getRawClient();
254
+ const localAccount = await webClient.getAccount(accountId);
240
255
  let accountForConfigRefresh: Account | null = localAccount ?? null;
241
256
 
242
257
  const guardianCommitment = normalizeHexWord(state.commitment);
243
258
  const localCommitment = localAccount
244
- ? normalizeHexWord(localAccount.commitment().toHex())
259
+ ? normalizeHexWord(localAccount.to_commitment().toHex())
245
260
  : null;
246
261
 
247
262
  if (!localAccount || localCommitment !== guardianCommitment) {
248
263
  const accountBytes = base64ToUint8Array(state.stateDataBase64);
249
264
  const incomingAccount = Account.deserialize(accountBytes);
250
265
  await this.ensureSafeToOverwriteLocalState(incomingAccount, localAccount);
251
- await this.webClient.newAccount(incomingAccount, true);
266
+ await webClient.newAccount(incomingAccount, true);
252
267
  accountForConfigRefresh = incomingAccount;
253
268
  }
254
269
 
@@ -259,7 +274,8 @@ export class Multisig {
259
274
 
260
275
  async verifyStateCommitment(): Promise<AccountStateVerificationResult> {
261
276
  const accountId = AccountId.fromHex(this._accountId);
262
- const localAccount = await this.webClient.getAccount(accountId);
277
+ const webClient = await this.getRawClient();
278
+ const localAccount = await webClient.getAccount(accountId);
263
279
 
264
280
  if (!localAccount) {
265
281
  throw new Error(
@@ -267,7 +283,7 @@ export class Multisig {
267
283
  );
268
284
  }
269
285
 
270
- const localCommitment = normalizeHexWord(localAccount.commitment().toHex());
286
+ const localCommitment = normalizeHexWord(localAccount.to_commitment().toHex());
271
287
  const onChainCommitment = await this.getOnChainCommitment(accountId);
272
288
 
273
289
  if (!onChainCommitment) {
@@ -308,7 +324,7 @@ export class Multisig {
308
324
  return;
309
325
  }
310
326
 
311
- const incomingCommitment = normalizeHexWord(incomingAccount.commitment().toHex());
327
+ const incomingCommitment = normalizeHexWord(incomingAccount.to_commitment().toHex());
312
328
  if (incomingCommitment !== onChainCommitment) {
313
329
  throw new Error(
314
330
  `Refusing to overwrite local state: incoming commitment does not match on-chain commitment for account ${this._accountId}`
@@ -472,17 +488,18 @@ export class Multisig {
472
488
  nonce?: number,
473
489
  newThreshold?: number,
474
490
  ): Promise<Proposal> {
491
+ const webClient = await this.getRawClient();
475
492
  const targetThreshold = newThreshold ?? this.threshold;
476
493
  const targetSignerCommitments = [...this.signerCommitments, newCommitment];
477
494
 
478
495
  const { request, salt } = await buildUpdateSignersTransactionRequest(
479
- this.webClient,
496
+ webClient,
480
497
  targetThreshold,
481
498
  targetSignerCommitments,
482
499
  { signatureScheme: this.signer.scheme },
483
500
  );
484
501
 
485
- const summary = await executeForSummary(this.webClient, this._accountId, request);
502
+ const summary = await executeForSummary(webClient, this._accountId, request);
486
503
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
487
504
  const proposalNonce = nonce ?? Date.now();
488
505
 
@@ -510,6 +527,7 @@ export class Multisig {
510
527
  nonce?: number,
511
528
  newThreshold?: number,
512
529
  ): Promise<Proposal> {
530
+ const webClient = await this.getRawClient();
513
531
  const normalizedRemove = signerToRemove.toLowerCase();
514
532
  const targetSignerCommitments = this.signerCommitments.filter(
515
533
  (c) => c.toLowerCase() !== normalizedRemove
@@ -531,13 +549,13 @@ export class Multisig {
531
549
  }
532
550
 
533
551
  const { request, salt } = await buildUpdateSignersTransactionRequest(
534
- this.webClient,
552
+ webClient,
535
553
  targetThreshold,
536
554
  targetSignerCommitments,
537
555
  { signatureScheme: this.signer.scheme },
538
556
  );
539
557
 
540
- const summary = await executeForSummary(this.webClient, this._accountId, request);
558
+ const summary = await executeForSummary(webClient, this._accountId, request);
541
559
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
542
560
  const proposalNonce = nonce ?? Date.now();
543
561
 
@@ -563,6 +581,7 @@ export class Multisig {
563
581
  newThreshold: number,
564
582
  nonce?: number,
565
583
  ): Promise<Proposal> {
584
+ const webClient = await this.getRawClient();
566
585
  if (newThreshold < 1 || newThreshold > this.signerCommitments.length) {
567
586
  throw new Error(
568
587
  `Invalid threshold ${newThreshold}. Must be between 1 and ${this.signerCommitments.length}`
@@ -574,13 +593,13 @@ export class Multisig {
574
593
  }
575
594
 
576
595
  const { request, salt } = await buildUpdateSignersTransactionRequest(
577
- this.webClient,
596
+ webClient,
578
597
  newThreshold,
579
598
  this.signerCommitments,
580
599
  { signatureScheme: this.signer.scheme },
581
600
  );
582
601
 
583
- const summary = await executeForSummary(this.webClient, this._accountId, request);
602
+ const summary = await executeForSummary(webClient, this._accountId, request);
584
603
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
585
604
  const proposalNonce = nonce ?? Date.now();
586
605
 
@@ -601,6 +620,7 @@ export class Multisig {
601
620
  targetThreshold: number,
602
621
  nonce?: number,
603
622
  ): Promise<Proposal> {
623
+ const webClient = await this.getRawClient();
604
624
  if (targetThreshold < 0 || targetThreshold > this.signerCommitments.length) {
605
625
  throw new Error(
606
626
  `Invalid threshold ${targetThreshold}. Must be between 0 and ${this.signerCommitments.length}`
@@ -619,13 +639,13 @@ export class Multisig {
619
639
  }
620
640
 
621
641
  const { request, salt } = await buildUpdateProcedureThresholdTransactionRequest(
622
- this.webClient,
642
+ webClient,
623
643
  targetProcedure,
624
644
  targetThreshold,
625
645
  { signatureScheme: this.signer.scheme },
626
646
  );
627
647
 
628
- const summary = await executeForSummary(this.webClient, this._accountId, request);
648
+ const summary = await executeForSummary(webClient, this._accountId, request);
629
649
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
630
650
  const proposalNonce = nonce ?? Date.now();
631
651
  const action = targetThreshold === 0
@@ -656,15 +676,16 @@ export class Multisig {
656
676
  newGuardianPubkey: string,
657
677
  nonce?: number,
658
678
  ): Promise<Proposal> {
679
+ const webClient = await this.getRawClient();
659
680
  await this.verifyGuardianEndpointCommitment(newGuardianEndpoint, newGuardianPubkey);
660
681
 
661
682
  const { request, salt } = await buildUpdateGuardianTransactionRequest(
662
- this.webClient,
683
+ webClient,
663
684
  newGuardianPubkey,
664
685
  { signatureScheme: this.signer.scheme },
665
686
  );
666
687
 
667
- const summary = await executeForSummary(this.webClient, this._accountId, request);
688
+ const summary = await executeForSummary(webClient, this._accountId, request);
668
689
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
669
690
  const proposalNonce = nonce ?? Date.now();
670
691
 
@@ -702,13 +723,14 @@ export class Multisig {
702
723
  noteIds: string[],
703
724
  nonce?: number,
704
725
  ): Promise<Proposal> {
726
+ const webClient = await this.getRawClient();
705
727
  if (noteIds.length === 0) {
706
728
  throw new Error('At least one note ID is required');
707
729
  }
708
730
 
709
- const { request, salt } = await buildConsumeNotesTransactionRequest(this.webClient, noteIds);
731
+ const { request, salt } = await buildConsumeNotesTransactionRequest(webClient, noteIds);
710
732
 
711
- const summary = await executeForSummary(this.webClient, this._accountId, request);
733
+ const summary = await executeForSummary(webClient, this._accountId, request);
712
734
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
713
735
  const proposalNonce = nonce ?? Date.now();
714
736
 
@@ -737,6 +759,7 @@ export class Multisig {
737
759
  amount: bigint,
738
760
  nonce?: number,
739
761
  ): Promise<Proposal> {
762
+ const webClient = await this.getRawClient();
740
763
  if (amount <= 0n) {
741
764
  throw new Error('Amount must be greater than 0');
742
765
  }
@@ -748,7 +771,7 @@ export class Multisig {
748
771
  amount,
749
772
  );
750
773
 
751
- const summary = await executeForSummary(this.webClient, this._accountId, request);
774
+ const summary = await executeForSummary(webClient, this._accountId, request);
752
775
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
753
776
  const proposalNonce = nonce ?? Date.now();
754
777
 
@@ -773,9 +796,10 @@ export class Multisig {
773
796
  */
774
797
  async getConsumableNotes(): Promise<ConsumableNote[]> {
775
798
  const accountId = AccountId.fromHex(this._accountId);
799
+ const webClient = await this.getRawClient();
776
800
 
777
801
  // Get consumable notes for this account
778
- const consumableRecords = await this.webClient.getConsumableNotes(accountId);
802
+ const consumableRecords = await webClient.getConsumableNotes(accountId);
779
803
 
780
804
  // Convert to our simplified ConsumableNote type
781
805
  const notes: ConsumableNote[] = [];
@@ -866,24 +890,73 @@ export class Multisig {
866
890
  return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
867
891
  }
868
892
 
893
+ async createTransactionProposalRequest(proposalId: string): Promise<TransactionRequest> {
894
+ const { finalRequest } = await this.prepareProposalExecution(proposalId);
895
+ return finalRequest;
896
+ }
897
+
869
898
  /**
870
899
  * Execute a proposal that has enough signatures.
871
900
  *
872
901
  * @param proposalId - The proposal commitment/ID
873
902
  */
874
903
  async executeProposal(proposalId: string): Promise<void> {
875
- const proposal = this.proposals.get(proposalId);
904
+ const { metadata, finalRequest, proposal } = await this.prepareProposalExecution(proposalId);
905
+
906
+ const accountId = AccountId.fromHex(this._accountId);
907
+ await this.midenClient.transactions.submit(accountId, finalRequest);
908
+
909
+ if (metadata.proposalType === 'switch_guardian') {
910
+ if (!metadata.newGuardianEndpoint || !metadata.newGuardianPubkey) {
911
+ throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
912
+ }
913
+
914
+ try {
915
+ const webClient = await this.getRawClient();
916
+ await webClient.syncState();
917
+
918
+ const updatedAccount = await webClient.getAccount(accountId);
919
+ if (!updatedAccount) {
920
+ throw new Error(
921
+ `Updated account ${this._accountId} is missing from local client`
922
+ );
923
+ }
924
+
925
+ const updatedStateBase64 = uint8ArrayToBase64(updatedAccount.serialize());
926
+ const nextGuardian = new GuardianHttpClient(metadata.newGuardianEndpoint);
927
+ this.setGuardianClient(nextGuardian);
928
+ this.guardianPublicKey = metadata.newGuardianPubkey;
929
+
930
+ await this.registerOnGuardian(updatedStateBase64);
931
+ } catch (error) {
932
+ const message = error instanceof Error ? error.message : String(error);
933
+ throw new Error(
934
+ `Transaction executed successfully but failed to register on new GUARDIAN: ${message}`
935
+ );
936
+ }
937
+ }
938
+
939
+ proposal.status = 'finalized';
940
+ }
941
+
942
+ private getLocalProposal(proposalId: string): Proposal | undefined {
943
+ const normalizedProposalId = normalizeHexWord(proposalId);
944
+ return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
945
+ }
946
+
947
+ private async prepareProposalExecution(
948
+ proposalId: string,
949
+ ): Promise<{ finalRequest: TransactionRequest; metadata: ProposalMetadata; proposal: Proposal }> {
950
+ const proposal = this.getLocalProposal(proposalId);
876
951
  if (!proposal) {
877
952
  throw new Error(`Proposal not found: ${proposalId}`);
878
953
  }
879
954
 
955
+ this.proposalFactory().assertAccountId(proposal.accountId);
880
956
  await this.verifyProposalMetadataBinding(proposal);
881
957
 
882
- const proposalType = proposal.metadata?.proposalType;
883
- const effectiveThreshold = proposalType
884
- ? this.getEffectiveThreshold(proposalType)
885
- : this.threshold;
886
-
958
+ const metadata = proposal.metadata;
959
+ const effectiveThreshold = this.getEffectiveThreshold(metadata.proposalType);
887
960
  const signatureContext = `Invalid proposal signatures for ${proposalId}`;
888
961
  const signaturesForExecution = new ProposalSignatures(
889
962
  proposal.signatures,
@@ -895,7 +968,8 @@ export class Multisig {
895
968
  throw new Error('Proposal is not ready for execution. Still pending signatures.');
896
969
  }
897
970
 
898
- const isSwitchGuardian = proposalType === 'switch_guardian';
971
+ const isSwitchGuardian = metadata.proposalType === 'switch_guardian';
972
+ const normalizedProposalId = normalizeHexWord(proposal.id);
899
973
 
900
974
  let txSummaryBase64: string;
901
975
  let delta: DeltaObject | undefined;
@@ -903,7 +977,7 @@ export class Multisig {
903
977
  if (isSwitchGuardian) {
904
978
  txSummaryBase64 = proposal.txSummary;
905
979
  } else {
906
- delta = await this.guardian.getDeltaProposal(this._accountId, proposalId);
980
+ delta = await this.guardian.getDeltaProposal(this._accountId, normalizedProposalId);
907
981
  txSummaryBase64 = delta.deltaPayload.txSummary.data;
908
982
  }
909
983
 
@@ -911,12 +985,13 @@ export class Multisig {
911
985
  const txSummary = TransactionSummary.deserialize(txSummaryBytes);
912
986
  const saltHex = txSummary.salt().toHex();
913
987
  const txCommitmentHex = txSummary.toCommitment().toHex();
988
+ const normalizedTxCommitmentHex = normalizeHexWord(txCommitmentHex);
914
989
  const normalizedSignerCommitments = new Set(
915
990
  this.signerCommitments.map((commitment) => normalizeHexWord(commitment)),
916
991
  );
917
-
918
992
  const adviceMap = new AdviceMap();
919
993
  const adviceMapKeys = new Set<string>();
994
+ const createTxCommitmentWord = (): Word => Word.fromHex(normalizedTxCommitmentHex);
920
995
 
921
996
  for (const cosignerSig of signaturesForExecution) {
922
997
  let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
@@ -949,10 +1024,9 @@ export class Multisig {
949
1024
  cosignerSig.signature.scheme,
950
1025
  );
951
1026
  const signature = Signature.deserialize(sigBytes);
952
- const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
953
1027
  const { key, values } = buildSignatureAdviceEntry(
954
1028
  signerCommitment,
955
- txCommitment,
1029
+ createTxCommitmentWord(),
956
1030
  signature,
957
1031
  ecdsaPublicKey,
958
1032
  cosignerSig.signature.scheme === 'ecdsa'
@@ -993,10 +1067,9 @@ export class Multisig {
993
1067
  }
994
1068
  const ackSigBytes = signatureHexToBytes(ackSigHex, ackScheme);
995
1069
  const ackSignature = Signature.deserialize(ackSigBytes);
996
- const txCommitmentForAck = Word.fromHex(normalizeHexWord(txCommitmentHex));
997
1070
  const { key: ackKey, values: ackValues } = buildSignatureAdviceEntry(
998
1071
  guardianCommitment,
999
- txCommitmentForAck,
1072
+ createTxCommitmentWord(),
1000
1073
  ackSignature,
1001
1074
  ackScheme === 'ecdsa' ? ackPubkey : undefined,
1002
1075
  ackScheme === 'ecdsa' ? ackSigHex : undefined,
@@ -1009,13 +1082,10 @@ export class Multisig {
1009
1082
  adviceMap.insert(ackKey, new FeltArray(ackValues));
1010
1083
  }
1011
1084
 
1012
- const metadata = proposal.metadata;
1013
- if (!metadata) {
1014
- throw new Error('Proposal missing metadata');
1015
- }
1016
1085
  if (metadata.proposalType === 'switch_guardian') {
1017
1086
  await this.verifyGuardianEndpointCommitment(metadata.newGuardianEndpoint, metadata.newGuardianPubkey);
1018
1087
  }
1088
+
1019
1089
  const executionSalt = Word.fromHex(normalizeHexWord(saltHex));
1020
1090
  const finalRequest = await this.buildTransactionRequestFromMetadata(
1021
1091
  metadata,
@@ -1023,42 +1093,7 @@ export class Multisig {
1023
1093
  adviceMap,
1024
1094
  );
1025
1095
 
1026
- const accountId = AccountId.fromHex(this._accountId);
1027
- const result = await this.webClient.executeTransaction(accountId, finalRequest);
1028
- const proven = await this.webClient.proveTransaction(result, null);
1029
- const submissionHeight = await this.webClient.submitProvenTransaction(proven, result);
1030
- await this.webClient.applyTransaction(result, submissionHeight);
1031
-
1032
- if (metadata.proposalType === 'switch_guardian') {
1033
- if (!metadata.newGuardianEndpoint || !metadata.newGuardianPubkey) {
1034
- throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
1035
- }
1036
-
1037
- try {
1038
- await this.webClient.syncState();
1039
-
1040
- const updatedAccount = await this.webClient.getAccount(accountId);
1041
- if (!updatedAccount) {
1042
- throw new Error(
1043
- `Updated account ${this._accountId} is missing from local client`
1044
- );
1045
- }
1046
-
1047
- const updatedStateBase64 = uint8ArrayToBase64(updatedAccount.serialize());
1048
- const nextGuardian = new GuardianHttpClient(metadata.newGuardianEndpoint);
1049
- this.setGuardianClient(nextGuardian);
1050
- this.guardianPublicKey = metadata.newGuardianPubkey;
1051
-
1052
- await this.registerOnGuardian(updatedStateBase64);
1053
- } catch (error) {
1054
- const message = error instanceof Error ? error.message : String(error);
1055
- throw new Error(
1056
- `Transaction executed successfully but failed to register on new GUARDIAN: ${message}`
1057
- );
1058
- }
1059
- }
1060
-
1061
- proposal.status = 'finalized';
1096
+ return { finalRequest, metadata, proposal };
1062
1097
  }
1063
1098
 
1064
1099
  /**
@@ -1236,7 +1271,8 @@ export class Multisig {
1236
1271
  : summary.salt();
1237
1272
 
1238
1273
  const request = await this.buildTransactionRequestFromMetadata(proposal.metadata, salt);
1239
- const reconstructed = await executeForSummary(this.webClient, this._accountId, request);
1274
+ const webClient = await this.getRawClient();
1275
+ const reconstructed = await executeForSummary(webClient, this._accountId, request);
1240
1276
  const reconstructedCommitment = normalizeHexWord(reconstructed.toCommitment().toHex());
1241
1277
 
1242
1278
  if (reconstructedCommitment !== txSummaryCommitment) {
@@ -1251,12 +1287,14 @@ export class Multisig {
1251
1287
  salt: Word,
1252
1288
  signatureAdviceMap?: AdviceMap,
1253
1289
  ): Promise<TransactionRequest> {
1290
+ const webClient = await this.getRawClient();
1291
+
1254
1292
  switch (metadata.proposalType) {
1255
1293
  case 'add_signer':
1256
1294
  case 'remove_signer':
1257
1295
  case 'change_threshold': {
1258
1296
  const { request } = await buildUpdateSignersTransactionRequest(
1259
- this.webClient,
1297
+ webClient,
1260
1298
  metadata.targetThreshold,
1261
1299
  metadata.targetSignerCommitments,
1262
1300
  { salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
@@ -1265,7 +1303,7 @@ export class Multisig {
1265
1303
  }
1266
1304
  case 'switch_guardian': {
1267
1305
  const { request } = await buildUpdateGuardianTransactionRequest(
1268
- this.webClient,
1306
+ webClient,
1269
1307
  metadata.newGuardianPubkey,
1270
1308
  { salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
1271
1309
  );
@@ -1273,7 +1311,7 @@ export class Multisig {
1273
1311
  }
1274
1312
  case 'update_procedure_threshold': {
1275
1313
  const { request } = await buildUpdateProcedureThresholdTransactionRequest(
1276
- this.webClient,
1314
+ webClient,
1277
1315
  metadata.targetProcedure,
1278
1316
  metadata.targetThreshold,
1279
1317
  { salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
@@ -1282,7 +1320,7 @@ export class Multisig {
1282
1320
  }
1283
1321
  case 'consume_notes': {
1284
1322
  const { request } = await buildConsumeNotesTransactionRequest(
1285
- this.webClient,
1323
+ webClient,
1286
1324
  metadata.noteIds,
1287
1325
  { salt, signatureAdviceMap }
1288
1326
  );
package/src/procedures.ts CHANGED
@@ -11,13 +11,13 @@
11
11
  * human-readable encoding and should not be copied into this table.
12
12
  */
13
13
  export const PROCEDURE_ROOTS = {
14
- update_signers: '0xafdea9abee19b02620520b11a165267b266403fa408f7eebc91fc83c33b507ca',
15
- update_procedure_threshold: '0xd6616423eebe012655a9db925363cc6751b94109a938e9bc5f488b52f75b7d1c',
16
- auth_tx: '0x611abcd570631ad98842cb6f0ef891fe8f9ee508b3245c55a5531d5a8f7fdca9',
17
- update_guardian: '0xcda1f9120a3ab2948d5cdc6b4b2982571c04e3f6af787a6d6b2f88eeedd872d7',
18
- verify_guardian: '0xe4be6069ba56570afecab5ae2ff77165d5230b703ba2cff1cafe06b92aa19e56',
19
- send_asset: '0x0e406b067ed2bcd7de745ca6517f519fd1a9be245f913347ac673ca1db30c1d6',
20
- receive_asset: '0x6f4bdbdc4b13d7ed933d590d88ac9dfb98020c9e917697845b5e169395b76a01',
14
+ update_signers: '0x3d382ad461f9914c487c6fe908991d088eb54ecbd4aa8560ef79c66c3746bf19',
15
+ update_procedure_threshold: '0x1f43e9d56ceff5d547ffdcb89896fb38cae0be1b74d9235ed2b4aa525df85f8d',
16
+ auth_tx: '0x415530d7169f849d7219e810065f9119bba9af2c55070de0bf4f082a1c0aea5c',
17
+ update_guardian: '0xc8ea876f1837e5cd1d6031becdbd40ce262ecd55930d65400f6890a37149d80c',
18
+ verify_guardian: '0x9bc6e7b25c8dbaa29d6ad41e354a545dd0a4bac7f3a521bb5195ba101f0213cc',
19
+ send_asset: '0x6d30df4312a2c44ec842db1bee227cc045396ca91e2c47d756dcb607f2bf5f89',
20
+ receive_asset: '0x75f638c65584d058542bcf4674b066ae394183021bc9b44dc2fdd97d52f9bcfb',
21
21
  } as const;
22
22
 
23
23
  /**
@@ -34,7 +34,7 @@ export type ProcedureName = keyof typeof PROCEDURE_ROOTS;
34
34
  * @example
35
35
  * ```typescript
36
36
  * const root = getProcedureRoot('send_asset');
37
- * // '0x0e406b067ed2bcd7de745ca6517f519fd1a9be245f913347ac673ca1db30c1d6'
37
+ * // '0x6d30df4312a2c44ec842db1bee227cc045396ca91e2c47d756dcb607f2bf5f89'
38
38
  * ```
39
39
  */
40
40
  export function getProcedureRoot(name: ProcedureName): string {
@@ -0,0 +1,133 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { mockCreateClient } = vi.hoisted(() => ({
4
+ mockCreateClient: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('@miden-sdk/miden-sdk', () => ({
8
+ WasmWebClient: {
9
+ createClient: mockCreateClient,
10
+ },
11
+ }));
12
+
13
+ import {
14
+ compileTxScript,
15
+ getRawMidenClient,
16
+ getTransactionProver,
17
+ resolveMidenRpcEndpoint,
18
+ } from './raw-client.js';
19
+
20
+ describe('raw-client', () => {
21
+ beforeEach(() => {
22
+ mockCreateClient.mockReset();
23
+ });
24
+
25
+ it('defaults RPC endpoint resolution to devnet', () => {
26
+ expect(resolveMidenRpcEndpoint()).toBe('https://rpc.devnet.miden.io');
27
+ });
28
+
29
+ it('returns the default prover from a public MidenClient', () => {
30
+ const prover = { kind: 'devnet-prover' };
31
+ const client = {
32
+ accounts: {},
33
+ sync: vi.fn(),
34
+ defaultProver: prover,
35
+ storeIdentifier: vi.fn(() => 'browser-db'),
36
+ };
37
+
38
+ expect(getTransactionProver(client as any)).toBe(prover);
39
+ });
40
+
41
+ it('returns null for raw web clients', () => {
42
+ const rawClient = {
43
+ executeTransaction: vi.fn(),
44
+ proveTransaction: vi.fn(),
45
+ };
46
+
47
+ expect(getTransactionProver(rawClient as any)).toBeNull();
48
+ });
49
+
50
+ it('creates and caches a raw client for a public MidenClient', async () => {
51
+ const rawClient = { kind: 'raw-client' };
52
+ const client = {
53
+ accounts: {},
54
+ sync: vi.fn(),
55
+ defaultProver: null,
56
+ storeIdentifier: vi.fn(() => 'browser-db'),
57
+ };
58
+
59
+ mockCreateClient.mockResolvedValue(rawClient);
60
+
61
+ await expect(
62
+ getRawMidenClient(client as any, 'https://rpc.devnet.miden.io'),
63
+ ).resolves.toBe(rawClient);
64
+ await expect(
65
+ getRawMidenClient(client as any, 'https://rpc.devnet.miden.io'),
66
+ ).resolves.toBe(rawClient);
67
+
68
+ expect(mockCreateClient).toHaveBeenCalledTimes(1);
69
+ expect(mockCreateClient).toHaveBeenCalledWith(
70
+ 'https://rpc.devnet.miden.io',
71
+ undefined,
72
+ undefined,
73
+ 'browser-db',
74
+ );
75
+ });
76
+
77
+ it('uses the public compile resource when available', async () => {
78
+ const script = { kind: 'compiled-script' };
79
+ const client = {
80
+ accounts: {},
81
+ sync: vi.fn(),
82
+ compile: {
83
+ txScript: vi.fn().mockResolvedValue(script),
84
+ },
85
+ storeIdentifier: vi.fn(() => 'browser-db'),
86
+ };
87
+
88
+ await expect(
89
+ compileTxScript(
90
+ client as any,
91
+ 'begin end',
92
+ [{ namespace: 'auth::multisig', code: 'export.foo' }],
93
+ ),
94
+ ).resolves.toBe(script);
95
+
96
+ expect(client.compile.txScript).toHaveBeenCalledWith({
97
+ code: 'begin end',
98
+ libraries: [{ namespace: 'auth::multisig', code: 'export.foo' }],
99
+ });
100
+ expect(mockCreateClient).not.toHaveBeenCalled();
101
+ });
102
+
103
+ it('falls back to raw client compilation for low-level callers', async () => {
104
+ const compiledScript = { kind: 'compiled-script' };
105
+ const builtLibrary = { kind: 'built-library' };
106
+ const builder = {
107
+ buildLibrary: vi.fn().mockReturnValue(builtLibrary),
108
+ linkDynamicLibrary: vi.fn(),
109
+ linkStaticLibrary: vi.fn(),
110
+ compileTxScript: vi.fn().mockReturnValue(compiledScript),
111
+ };
112
+ const rawClient = {
113
+ createCodeBuilder: vi.fn().mockReturnValue(builder),
114
+ };
115
+
116
+ await expect(
117
+ compileTxScript(
118
+ rawClient as any,
119
+ 'begin end',
120
+ [
121
+ { namespace: 'auth::multisig', code: 'export.foo' },
122
+ { namespace: 'auth::guardian', code: 'export.bar', linking: 'static' },
123
+ ],
124
+ ),
125
+ ).resolves.toBe(compiledScript);
126
+
127
+ expect(builder.buildLibrary).toHaveBeenNthCalledWith(1, 'auth::multisig', 'export.foo');
128
+ expect(builder.buildLibrary).toHaveBeenNthCalledWith(2, 'auth::guardian', 'export.bar');
129
+ expect(builder.linkDynamicLibrary).toHaveBeenCalledWith(builtLibrary);
130
+ expect(builder.linkStaticLibrary).toHaveBeenCalledWith(builtLibrary);
131
+ expect(builder.compileTxScript).toHaveBeenCalledWith('begin end');
132
+ });
133
+ });