@openzeppelin/miden-multisig-client 0.13.3 → 0.13.4

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/src/multisig.ts CHANGED
@@ -866,24 +866,75 @@ export class Multisig {
866
866
  return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
867
867
  }
868
868
 
869
+ async createTransactionProposalRequest(proposalId: string): Promise<TransactionRequest> {
870
+ const { finalRequest } = await this.prepareProposalExecution(proposalId);
871
+ return finalRequest;
872
+ }
873
+
869
874
  /**
870
875
  * Execute a proposal that has enough signatures.
871
876
  *
872
877
  * @param proposalId - The proposal commitment/ID
873
878
  */
874
879
  async executeProposal(proposalId: string): Promise<void> {
875
- const proposal = this.proposals.get(proposalId);
880
+ const { metadata, finalRequest, proposal } = await this.prepareProposalExecution(proposalId);
881
+
882
+ const accountId = AccountId.fromHex(this._accountId);
883
+ const result = await this.webClient.executeTransaction(accountId, finalRequest);
884
+ const proven = await this.webClient.proveTransaction(result, null);
885
+ const submissionHeight = await this.webClient.submitProvenTransaction(proven, result);
886
+ await this.webClient.applyTransaction(result, submissionHeight);
887
+
888
+ if (metadata.proposalType === 'switch_guardian') {
889
+ if (!metadata.newGuardianEndpoint || !metadata.newGuardianPubkey) {
890
+ throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
891
+ }
892
+
893
+ try {
894
+ await this.webClient.syncState();
895
+
896
+ const updatedAccount = await this.webClient.getAccount(accountId);
897
+ if (!updatedAccount) {
898
+ throw new Error(
899
+ `Updated account ${this._accountId} is missing from local client`
900
+ );
901
+ }
902
+
903
+ const updatedStateBase64 = uint8ArrayToBase64(updatedAccount.serialize());
904
+ const nextGuardian = new GuardianHttpClient(metadata.newGuardianEndpoint);
905
+ this.setGuardianClient(nextGuardian);
906
+ this.guardianPublicKey = metadata.newGuardianPubkey;
907
+
908
+ await this.registerOnGuardian(updatedStateBase64);
909
+ } catch (error) {
910
+ const message = error instanceof Error ? error.message : String(error);
911
+ throw new Error(
912
+ `Transaction executed successfully but failed to register on new GUARDIAN: ${message}`
913
+ );
914
+ }
915
+ }
916
+
917
+ proposal.status = 'finalized';
918
+ }
919
+
920
+ private getLocalProposal(proposalId: string): Proposal | undefined {
921
+ const normalizedProposalId = normalizeHexWord(proposalId);
922
+ return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
923
+ }
924
+
925
+ private async prepareProposalExecution(
926
+ proposalId: string,
927
+ ): Promise<{ finalRequest: TransactionRequest; metadata: ProposalMetadata; proposal: Proposal }> {
928
+ const proposal = this.getLocalProposal(proposalId);
876
929
  if (!proposal) {
877
930
  throw new Error(`Proposal not found: ${proposalId}`);
878
931
  }
879
932
 
933
+ this.proposalFactory().assertAccountId(proposal.accountId);
880
934
  await this.verifyProposalMetadataBinding(proposal);
881
935
 
882
- const proposalType = proposal.metadata?.proposalType;
883
- const effectiveThreshold = proposalType
884
- ? this.getEffectiveThreshold(proposalType)
885
- : this.threshold;
886
-
936
+ const metadata = proposal.metadata;
937
+ const effectiveThreshold = this.getEffectiveThreshold(metadata.proposalType);
887
938
  const signatureContext = `Invalid proposal signatures for ${proposalId}`;
888
939
  const signaturesForExecution = new ProposalSignatures(
889
940
  proposal.signatures,
@@ -895,7 +946,8 @@ export class Multisig {
895
946
  throw new Error('Proposal is not ready for execution. Still pending signatures.');
896
947
  }
897
948
 
898
- const isSwitchGuardian = proposalType === 'switch_guardian';
949
+ const isSwitchGuardian = metadata.proposalType === 'switch_guardian';
950
+ const normalizedProposalId = normalizeHexWord(proposal.id);
899
951
 
900
952
  let txSummaryBase64: string;
901
953
  let delta: DeltaObject | undefined;
@@ -903,7 +955,7 @@ export class Multisig {
903
955
  if (isSwitchGuardian) {
904
956
  txSummaryBase64 = proposal.txSummary;
905
957
  } else {
906
- delta = await this.guardian.getDeltaProposal(this._accountId, proposalId);
958
+ delta = await this.guardian.getDeltaProposal(this._accountId, normalizedProposalId);
907
959
  txSummaryBase64 = delta.deltaPayload.txSummary.data;
908
960
  }
909
961
 
@@ -911,12 +963,13 @@ export class Multisig {
911
963
  const txSummary = TransactionSummary.deserialize(txSummaryBytes);
912
964
  const saltHex = txSummary.salt().toHex();
913
965
  const txCommitmentHex = txSummary.toCommitment().toHex();
966
+ const normalizedTxCommitmentHex = normalizeHexWord(txCommitmentHex);
914
967
  const normalizedSignerCommitments = new Set(
915
968
  this.signerCommitments.map((commitment) => normalizeHexWord(commitment)),
916
969
  );
917
-
918
970
  const adviceMap = new AdviceMap();
919
971
  const adviceMapKeys = new Set<string>();
972
+ const createTxCommitmentWord = (): Word => Word.fromHex(normalizedTxCommitmentHex);
920
973
 
921
974
  for (const cosignerSig of signaturesForExecution) {
922
975
  let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
@@ -949,10 +1002,9 @@ export class Multisig {
949
1002
  cosignerSig.signature.scheme,
950
1003
  );
951
1004
  const signature = Signature.deserialize(sigBytes);
952
- const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
953
1005
  const { key, values } = buildSignatureAdviceEntry(
954
1006
  signerCommitment,
955
- txCommitment,
1007
+ createTxCommitmentWord(),
956
1008
  signature,
957
1009
  ecdsaPublicKey,
958
1010
  cosignerSig.signature.scheme === 'ecdsa'
@@ -993,10 +1045,9 @@ export class Multisig {
993
1045
  }
994
1046
  const ackSigBytes = signatureHexToBytes(ackSigHex, ackScheme);
995
1047
  const ackSignature = Signature.deserialize(ackSigBytes);
996
- const txCommitmentForAck = Word.fromHex(normalizeHexWord(txCommitmentHex));
997
1048
  const { key: ackKey, values: ackValues } = buildSignatureAdviceEntry(
998
1049
  guardianCommitment,
999
- txCommitmentForAck,
1050
+ createTxCommitmentWord(),
1000
1051
  ackSignature,
1001
1052
  ackScheme === 'ecdsa' ? ackPubkey : undefined,
1002
1053
  ackScheme === 'ecdsa' ? ackSigHex : undefined,
@@ -1009,13 +1060,10 @@ export class Multisig {
1009
1060
  adviceMap.insert(ackKey, new FeltArray(ackValues));
1010
1061
  }
1011
1062
 
1012
- const metadata = proposal.metadata;
1013
- if (!metadata) {
1014
- throw new Error('Proposal missing metadata');
1015
- }
1016
1063
  if (metadata.proposalType === 'switch_guardian') {
1017
1064
  await this.verifyGuardianEndpointCommitment(metadata.newGuardianEndpoint, metadata.newGuardianPubkey);
1018
1065
  }
1066
+
1019
1067
  const executionSalt = Word.fromHex(normalizeHexWord(saltHex));
1020
1068
  const finalRequest = await this.buildTransactionRequestFromMetadata(
1021
1069
  metadata,
@@ -1023,42 +1071,7 @@ export class Multisig {
1023
1071
  adviceMap,
1024
1072
  );
1025
1073
 
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';
1074
+ return { finalRequest, metadata, proposal };
1062
1075
  }
1063
1076
 
1064
1077
  /**