miden_multisig_client/client/
io.rs1use guardian_client::delta_status::Status;
7use guardian_shared::SignatureScheme;
8
9use super::MultisigClient;
10use crate::error::{MultisigError, Result};
11use crate::export::{ExportedProposal, ExportedSignature};
12
13impl MultisigClient {
14 pub async fn export_proposal(
25 &mut self,
26 proposal_id: &str,
27 path: &std::path::Path,
28 ) -> Result<()> {
29 let exported = self.export_proposal_to_exported(proposal_id).await?;
30 let json = exported.to_json()?;
31 std::fs::write(path, json)
32 .map_err(|e| MultisigError::InvalidConfig(format!("failed to write file: {}", e)))?;
33 Ok(())
34 }
35
36 pub async fn export_proposal_to_string(&mut self, proposal_id: &str) -> Result<String> {
45 let exported = self.export_proposal_to_exported(proposal_id).await?;
46 exported.to_json()
47 }
48
49 async fn export_proposal_to_exported(&mut self, proposal_id: &str) -> Result<ExportedProposal> {
58 let account = self.require_account()?.clone();
59 let account_id = account.id();
60 let mut guardian_client = self.create_authenticated_guardian_client().await?;
61 let response = guardian_client
62 .get_delta_proposal(&account_id, proposal_id)
63 .await
64 .map_err(|e| MultisigError::GuardianServer(format!("failed to get proposal: {}", e)))?;
65 let raw_proposal = response
66 .proposal
67 .as_ref()
68 .ok_or_else(|| MultisigError::ProposalNotFound(proposal_id.to_string()))?;
69 Self::ensure_proposal_account_id(&raw_proposal.account_id, &account_id)?;
70 let proposal = crate::proposal::Proposal::from(raw_proposal)?;
71 self.verify_proposal_summary_binding(&proposal).await?;
72
73 let status = raw_proposal.status.as_ref().ok_or_else(|| {
75 MultisigError::GuardianServer(format!("proposal {} has no status field", proposal_id))
76 })?;
77
78 let status_oneof = status.status.as_ref().ok_or_else(|| {
79 MultisigError::GuardianServer(format!("proposal {} has empty status", proposal_id))
80 })?;
81
82 let pending = match status_oneof {
83 Status::Pending(p) => p,
84 _ => {
85 return Err(MultisigError::GuardianServer(format!(
86 "proposal {} is not in pending state",
87 proposal_id
88 )));
89 }
90 };
91
92 let mut signatures = Vec::new();
93 for cosigner_sig in pending.cosigner_sigs.iter() {
94 if let Some(ref sig) = cosigner_sig.signature {
95 let scheme = if sig.scheme.eq_ignore_ascii_case("ecdsa") {
96 SignatureScheme::Ecdsa
97 } else {
98 SignatureScheme::Falcon
99 };
100 signatures.push(ExportedSignature {
101 signer_commitment: cosigner_sig.signer_id.clone(),
102 signature: sig.signature.clone(),
103 scheme,
104 public_key_hex: sig.public_key.clone(),
105 });
106 }
107 }
108
109 let exported =
110 ExportedProposal::from_proposal(&proposal, account_id)?.with_signatures(signatures);
111
112 Ok(exported)
113 }
114
115 pub async fn import_proposal(&mut self, path: &std::path::Path) -> Result<ExportedProposal> {
127 let json = std::fs::read_to_string(path)
128 .map_err(|e| MultisigError::InvalidConfig(format!("failed to read file: {}", e)))?;
129 self.import_proposal_from_string(&json).await
130 }
131
132 pub async fn import_proposal_from_string(&mut self, json: &str) -> Result<ExportedProposal> {
140 let exported = ExportedProposal::from_json(json)?;
141 exported.validate(self.account.as_ref().map(|account| account.id()))?;
142
143 let proposal = exported.to_proposal()?;
144 self.verify_proposal_summary_binding(&proposal).await?;
145
146 Ok(exported)
147 }
148}