1use std::env::home_dir;
2use std::io::Write;
3use std::path::PathBuf;
4use log::info;
5use monero_rpc::BalanceData;
6use crate::monero::rpc_core::MoneroRpcWrapper;
7use crate::monero::rpc_multisig::{ExchangeMultisigKeysResult, MakeMultisigResult, TransferResult};
8use crate::word_pass_support::WordsPassNodeConfig;
9use redgold_common_no_wasm::ssh_like::{LocalSSHLike, SSHOrCommandLike, SSHProcessInvoke};
10use redgold_schema::conf::node_config::NodeConfig;
11use redgold_schema::errors::into_error::ToErrorInfo;
12use redgold_schema::observability::errors::Loggable;
13use redgold_schema::proto_serde::ProtoSerde;
14use redgold_schema::structs::{Address, CurrencyAmount, ErrorInfo, ExternalTransactionId, Hash, MoneroMultisigFormationRequest, MultipartyIdentifier, NetworkEnvironment, PublicKey, RoomId, SupportedCurrency, Weighting};
15use redgold_schema::message::Request;
16use redgold_schema::util::lang_util::{AnyPrinter};
17use redgold_schema::{ErrorInfoContext, RgResult, SafeOption, ShortString};
18use serde::{Deserialize, Serialize};
19use redgold_common::external_resources::PeerBroadcast;
20use redgold_schema::config_data::RpcUrl;
21use redgold_schema::helpers::easy_json::EasyJsonDeser;
22use crate::monero::to_address::ToMoneroAddress;
23use crate::TestConstants;
24use crate::util::mnemonic_support::MnemonicSupport;
25
26use super::rpc_multisig::{DescribeTransferResult, SignedMultisigTxset, TransferSplitResult};
27
28#[derive(Clone)]
35pub struct MoneroNodeRpcInterfaceWrapper<S: SSHOrCommandLike> {
36 pub wallet_rpc: MoneroRpcWrapper,
37 pub daemon_rpc: MoneroRpcWrapper,
38 pub state : MoneroWalletMultisigRpcState,
39 pub cmd: S,
40 pub wallet_dir: String,
41 pub wallet_expect_contents: String,
42 pub allow_deletes: bool,
43 pub create_states: Vec<MoneroWalletMultisigRpcState>,
44 pub history: Vec<StateHistoryItem>,
45}
46
47
48#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
49pub struct PartySecretInstanceData {
50 pub address: Address,
51 pub monero_history: Option<Vec<StateHistoryItem>>,
52}
53
54
55#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
56pub struct PartySecretData {
57 pub instances: Vec<PartySecretInstanceData>
58}
59
60
61#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
62pub struct StateHistoryItem {
63 pub input_state: MoneroWalletMultisigRpcState,
64 pub output_state: MoneroWalletMultisigRpcState,
65 pub input_peer_strings: Option<Vec<String>>,
66 pub input_threshold: Option<i64>,
67 pub restore_height: Option<i64>,
68}
69
70#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
71pub enum MoneroWalletMultisigRpcState {
72 #[default]
73 Unknown,
74 Prepared(String),
75 Made(MakeMultisigResult),
76 Exchanged(ExchangeMultisigKeysResult),
77 Finalized(String),
78 MultisigReadyToSend
79}
80
81impl MoneroWalletMultisigRpcState {
82
83 pub fn is_before_final_state(&self) -> bool {
84 match &self {
85 MoneroWalletMultisigRpcState::Made(_) => true,
86 _ => false
87 }
88 }
89 pub fn multisig_address_str(&self) -> Option<String> {
90 match self {
91 MoneroWalletMultisigRpcState::Unknown => None,
92 MoneroWalletMultisigRpcState::Prepared(_) => None,
93 MoneroWalletMultisigRpcState::Made(m) => Some(m.address.clone()),
94 MoneroWalletMultisigRpcState::Exchanged(e) => Some(e.address.clone()),
95 MoneroWalletMultisigRpcState::Finalized(f) => Some(f.clone()),
96 MoneroWalletMultisigRpcState::MultisigReadyToSend => {None}
97 }
98 }
99
100 pub fn multisig_typed_address_external(&self) -> Option<Address> {
101 self.multisig_address_str().map(|s| Address::from_monero_external(&s))
103 }
104
105 pub fn multisig_info_string(&self) -> Option<String> {
106 match self {
107 MoneroWalletMultisigRpcState::Unknown => {None}
108 MoneroWalletMultisigRpcState::Prepared(s) => {Some(s.clone())}
109 MoneroWalletMultisigRpcState::Made(m) => {Some(m.multisig_info.clone())}
110 MoneroWalletMultisigRpcState::Exchanged(e) => {Some(e.multisig_info.clone())}
111 MoneroWalletMultisigRpcState::Finalized(e) => { None }
112 _ => { None}
113 }
114 }
115
116}
117
118impl<S: SSHOrCommandLike> MoneroNodeRpcInterfaceWrapper<S> {
119
120
121 pub fn reset(&mut self) {
122 self.create_states = vec![];
123 self.history = vec![];
124 self.state = MoneroWalletMultisigRpcState::Unknown;
125 }
126
127 pub fn any_multisig_addr_creation(&self) -> Option<String> {
128 self.create_states.iter()
129 .filter_map(|s| s.multisig_address_str())
130 .filter(|s| !s.is_empty())
131 .next()
132 }
133
134 pub fn any_multisig_addr_history(&self) -> Option<String> {
135 self.history.iter()
136 .filter_map(|s| s.output_state.multisig_address_str())
137 .filter(|s| !s.is_empty())
138 .next()
139 }
140
141 pub fn from_config_local(
142 nc: &NodeConfig,
143 wallet_dir: impl Into<String>,
144 expect_path: impl Into<String>,
145 allow_deletes: Option<bool>,
146 ) -> Option<RgResult<MoneroNodeRpcInterfaceWrapper<LocalSSHLike>>> {
147 let local_ssh_like = LocalSSHLike::new(None);
148 MoneroNodeRpcInterfaceWrapper::<LocalSSHLike>::from_config(nc, local_ssh_like, wallet_dir, expect_path, allow_deletes)
149 }
150
151 pub fn from_config(
152 nc: &NodeConfig,
153 cmd: S,
154 wallet_dir: impl Into<String>,
155 expect_contents: impl Into<String>,
156 allow_deletes: Option<bool>,
157 ) -> Option<RgResult<MoneroNodeRpcInterfaceWrapper<S>>> {
158 let allow_deletes = allow_deletes.unwrap_or(false);
159 let wallet_dir = wallet_dir.into();
160 let daemon_opt = MoneroRpcWrapper::from_config(nc);
162 let wallet_opt = MoneroRpcWrapper::authed_from_config(nc);
164 let exp = expect_contents.into();
165
166 Self::from_daemons(cmd, allow_deletes, wallet_dir, exp, daemon_opt, wallet_opt)
167 }
168
169 fn from_daemons(
170 cmd: S,
171 allow_deletes: bool,
172 wallet_dir: String,
173 wallet_exp_path: String,
174 daemon_opt: Option<RgResult<MoneroRpcWrapper>>,
175 wallet_opt: Option<RgResult<MoneroRpcWrapper>>
176 ) -> Option<Result<MoneroNodeRpcInterfaceWrapper<S>, ErrorInfo>> {
177 daemon_opt.and_then(
178 |daemon_result|
179 wallet_opt.map(|wallet_result|
180 wallet_result.and_then(|wallet| daemon_result.map(|daemon|
181 MoneroNodeRpcInterfaceWrapper::<S> {
182 wallet_rpc: wallet,
183 daemon_rpc: daemon,
184 state: MoneroWalletMultisigRpcState::Unknown,
185 cmd,
186 wallet_dir,
187 wallet_expect_contents: wallet_exp_path,
188 allow_deletes,
189 create_states: vec![],
190 history: vec![],
191 }))
192 )
193 )
194 }
195
196 pub async fn set_as_multisig_self(&mut self, x: &String,
197 restore_height: Option<i64>,
198 restore_from_daemon_cur: bool
199 ) -> RgResult<()> {
200 let mut restore_height = restore_height;
201 if restore_height.is_none() && restore_from_daemon_cur {
202 restore_height = Some(self.daemon_rpc.daemon_height().await?);
203 println!("Setting restore height to {}", restore_height.clone().unwrap());
204 }
205 self.wallet_rpc.register_self_activate_ok(
206 Some(x.clone()),
207 restore_height,
208 ).await
209 }
210
211 fn multisig_filename_prefix() -> Option<String> {
212 Some("multisig".to_string())
213 }
214
215 pub async fn restore_from_history(
221 &mut self,
222 h: Vec<StateHistoryItem>, wallet_filename: &String
223 ) -> RgResult<()> {
224 let restore = h.iter().filter_map(|h| h.restore_height).min().unwrap_or(0);
225 self.prepare_wallet_fnm_and_set_multisig(wallet_filename, Some(restore), false).await?;
226 let mut m = self.wallet_rpc.get_multisig()?;
227 let is_ms = m.is_multisig().await?;
228 for item in h.iter() {
232 self.multisig_create_next(
233 item.input_peer_strings.clone(),
234 item.input_threshold.clone(),
235 wallet_filename,
236 true,
237 2
238 ).await?;
239 }
240 Ok(())
241 }
242
243 pub fn get_secret(&self) -> RgResult<PartySecretInstanceData> {
244 Ok(PartySecretInstanceData {
245 address: Address::from_monero_external(&self.any_multisig_addr_creation().ok_msg("No multisig address found")?),
246 monero_history: Some(self.history.clone()),
247 })
248 }
249
250 pub async fn start_multisig_creation(
266 &mut self,
267 wallet_filename: &String,
268 skip_init: bool
269 ) -> RgResult<String> {
270
271 if !skip_init {
272 self.prepare_wallet_fnm_and_set_multisig(wallet_filename, None, true).await?;
273 }
274 let mut m = self.wallet_rpc.get_multisig()?;
275 let is_ms = m.is_multisig().await?;
276 println!("Is multisig: {:?}", is_ms);
277 let prepare_result = m.prepare_multisig().await?;
283 self.state = MoneroWalletMultisigRpcState::Prepared(prepare_result.clone());
294
295 println!("Created wallet for peer {:?}", prepare_result.clone());
297
298 Ok(prepare_result)
299 }
300
301 pub async fn refresh_sync_check_daemon_against_wallet(&self) -> RgResult<i64> {
302 let wallet = self.wallet_rpc.clone();
303 self.daemon_rpc.refresh_sync_check_daemon_against_wallet(wallet).await
304 }
305
306 pub async fn get_balance_all_info(&self) -> RgResult<BalanceData> {
307 self.wallet_rpc.clone().client.wallet().get_balance(0, None).await
308 .map_err(|e| "Failed to get balance".to_error_info().enhance(e.to_string()))
309 }
310
311
312 pub async fn prepare_wallet_fnm_and_set_multisig(
313 &mut self,
314 wallet_filename_relative: &String,
315 restore_height: Option<i64>,
316 restore_from_daemon_cur: bool
317 ) -> Result<(), ErrorInfo> {
318 let wallet_directory = self.wallet_dir.clone();
319
320 println!("Wallet relative filename on prepare and open: {:?}", wallet_filename_relative.clone());
321 println!("Wallet directory on prepare and open: {:?}", wallet_directory.clone());
322 self.wallet_rpc.close_wallet().await.log_error().ok();
323 self.state = MoneroWalletMultisigRpcState::Unknown;
330 self.set_as_multisig_self(
331 &wallet_filename_relative,
332 restore_height,
333 restore_from_daemon_cur
334 ).await?;
335
336 self.wallet_rpc.close_wallet().await.log_error().ok();
338 let remote_wallet_filename = format!("{}/{}", wallet_directory, wallet_filename_relative.clone());
344 let exp = self.wallet_expect_contents.replace("{{WALLET_FILENAME}}", &*remote_wallet_filename);
345 let local_fnm = "wallet.exp";
346 std::fs::remove_file(local_fnm).ok();
347 std::fs::write(local_fnm, exp).error_info("Failed to write expect file")?;
348 let expect_remote_filename = format!("{}_wallet.exp", remote_wallet_filename);
349 let copied = self.cmd.scp(local_fnm, expect_remote_filename.clone(), true, None).await?;
350 println!("Copied expect file: {:?} to dest {}", copied.clone(), expect_remote_filename.clone());
351
352 let expect_cmd = format!("expect {}", expect_remote_filename.clone());
353 println!("expect command: {:?}", expect_cmd.clone());
354 let result = self.cmd.execute(expect_cmd, None).await?;
355 if result.find("enable-multisig-experimental = 0").is_some() {
356 println!("Failed to enable multisig: {}", result.clone());
357 }
358 else {
359 println!("Enabled multisig: {}", result.clone());
360 }
361 println!("Worked!");
362 self.wallet_rpc.open_wallet_filename_exact_no_prefix(wallet_filename_relative.clone()).await?;
363 Ok(())
364 }
365
366
367 pub async fn multisig_create_loop<B>(
368 &mut self,
369 all_pks: &Vec<PublicKey>,
370 threshold: i64,
371 peer_broadcast: &B
372 ) -> RgResult<PartySecretInstanceData> where B: PeerBroadcast {
373 let b = Self::get_wallet_filename_id(all_pks, threshold);
374 let wallet_filename = b;
375 let mut peer_strs = vec![];
376 loop {
377 let next = self.multisig_create_next(
378 Some(peer_strs.clone()), Some(threshold), &wallet_filename, false, 2).await?;
379 if next == MoneroWalletMultisigRpcState::MultisigReadyToSend {
380 break;
381 }
382 let info_str = next.multisig_info_string().ok_msg("No multisig info string from self")?;
383 let mut req = Request::default();
384 req.monero_multisig_formation_request = Some(MoneroMultisigFormationRequest {
385 public_keys: all_pks.clone(),
386 threshold: Some(Weighting::from_int_basis(threshold, all_pks.len() as i64)),
387 peer_strings: peer_strs.clone(),
388 });
389
390 let mut new_peer_strs = vec![info_str];
391 for r in peer_broadcast.broadcast(&all_pks.clone(), req).await? {
393 let r = r?;
394 let r = r.monero_multisig_formation_response.ok_msg("No response from peer")?;
395 new_peer_strs.push(r);
396 }
397 peer_strs = new_peer_strs;
398 }
399 self.get_secret()
400 }
401
402 pub fn get_wallet_filename_id(all_pks: &Vec<PublicKey>, threshold: i64) -> String {
403 let mut wallet_ident = vec![];
404 all_pks.iter().for_each(|pk| wallet_ident.extend(pk.vec()));
405 threshold.to_le_bytes().iter().for_each(|b| wallet_ident.push(*b));
406 let b = Hash::digest(wallet_ident).raw_bytes_hex().unwrap();
407 b
408 }
409
410 pub async fn multisig_create_next(
411 &mut self,
412 peer_strings: Option<Vec<String>>,
413 threshold: Option<i64>,
414 wallet_filename: &String,
415 skip_initial: bool,
416 max_num_rounds: i64
417 ) -> RgResult<MoneroWalletMultisigRpcState> {
418
419 let mut history_item = StateHistoryItem {
420 input_state: self.state.clone(),
421 output_state: MoneroWalletMultisigRpcState::Unknown,
422 input_peer_strings: peer_strings.clone(),
423 input_threshold: threshold.clone(),
424 restore_height: Some(self.daemon_rpc.daemon_height().await?),
425 };
426 match self.state.clone() {
427 MoneroWalletMultisigRpcState::Unknown => {
428 println!("starting");
429 self.start_multisig_creation(&wallet_filename, skip_initial).await?;
430 println!("Finished preparing")
431 }
432 MoneroWalletMultisigRpcState::Prepared(_) => {
433 println!("making");
434 let res = self.make_multisig(
435 peer_strings.ok_msg("missing peer strings in make multisig")?,
436 threshold.ok_msg("Missing threshold on make multisig")?
437 ).await?;
438 self.state = MoneroWalletMultisigRpcState::Made(res.clone());
439 }
440 MoneroWalletMultisigRpcState::Made(_) => {
441 let mut res = self.exchange_multisig_keys(
448 peer_strings.ok_msg("missing peer strings in exchange multisig")?
449 ).await?;
450 res.input_round = 0;
451 res.output_round = 1;
452 self.state = MoneroWalletMultisigRpcState::Exchanged(res.clone());
453 }
454 MoneroWalletMultisigRpcState::Exchanged(e) => {
455 if e.output_round == max_num_rounds {
456 self.state = MoneroWalletMultisigRpcState::MultisigReadyToSend;
457 } else {
458 let mut res = self.exchange_multisig_keys(
459 peer_strings.ok_msg("missing peer strings in exchange multisig")?
460 ).await?;
461 res.input_round = e.output_round;
462 res.output_round = e.output_round + 1;
463 self.state = MoneroWalletMultisigRpcState::Exchanged(res.clone());
464 }
465 }
472 MoneroWalletMultisigRpcState::Finalized(a) => {
473 self.state = MoneroWalletMultisigRpcState::MultisigReadyToSend;
474 }
475 MoneroWalletMultisigRpcState::MultisigReadyToSend => {
476 return "Multisig wallet is ready to send transactions, but create next called".to_error()
477 }
478 }
479 self.create_states.push(self.state.clone());
480 history_item.output_state = self.state.clone();
481 self.history.push(history_item);
482 Ok(self.state.clone())
483 }
484
485 fn _id_to_filename(id: &MultipartyIdentifier) -> String {
486 let string = id.proto_serialize_hex().last_n(12).unwrap();
487 format!("{}_{}", Self::multisig_filename_prefix().unwrap(), string)
488 }
489
490 pub async fn multisig_send_prepare_and_sign(
491 &mut self,
492 amounts: Vec<(Address, CurrencyAmount)>
493 ) -> RgResult<(TransferResult, SignedMultisigTxset)> {
494 let mut m: super::rpc_multisig::MoneroWalletRpcMultisigClient = self.wallet_rpc.get_multisig()?;
495
496 let mut vec = vec![];
497
498 for (addr, amount) in amounts.iter() {
499 vec.push((addr.render_string()?, amount.amount as u64));
500 }
501 let tx = m.transfer(vec, None, None).await?;
503
504 let signed = m.sign_multisig(tx.multisig_txset.clone()).await?;
506
507 Ok((tx, signed))
508 }
509
510 pub async fn make_multisig(&mut self, peer_strings: Vec<String>, threshold: i64) -> RgResult<MakeMultisigResult> {
511 let mut m = self.wallet_rpc.get_multisig()?;
512 let make_result = m.make_multisig(peer_strings, threshold as u32, "".to_string()).await?;
513 Ok(make_result)
514 }
515
516 pub async fn exchange_multisig_keys(&mut self, peer_strings: Vec<String>) -> RgResult<ExchangeMultisigKeysResult> {
517 let mut m = self.wallet_rpc.get_multisig()?;
518 let exchanged = m.exchange_multisig_keys(peer_strings, "".to_string(), None).await?;
519 Ok(exchanged)
520 }
521
522 pub async fn export_multisig_info(&mut self) -> RgResult<String> {
532 let mut m = self.wallet_rpc.get_multisig()?;
533 Ok(m.export_multisig_info().await?)
534 }
535
536 pub async fn import_multisig_info(&mut self, info: Vec<String>) -> RgResult<u64> {
538 let mut m = self.wallet_rpc.get_multisig()?;
539 Ok(m.import_multisig_info(info).await?)
540 }
541
542 pub async fn submit_multisig_transaction(&mut self, tx_data_hex: String) -> RgResult<Vec<String>> {
544 let mut m = self.wallet_rpc.get_multisig()?;
545 Ok(m.submit_multisig(tx_data_hex).await?)
546 }
547
548 pub async fn describe_multisig_transaction(&mut self, unsigned_txset: String) -> RgResult<DescribeTransferResult> {
550 let mut m = self.wallet_rpc.get_multisig()?;
551 Ok(m.describe_transfer(unsigned_txset).await?)
552 }
553
554}
555
556
557pub fn rpcs(seed_id: i64) -> Vec<RpcUrl> {
558 let password = std::env::var("MONERO_TEST_RPC_PASSWORD").unwrap();
559 vec![
560 RpcUrl{
561 currency: SupportedCurrency::Monero,
562 url: format!("http://server:28{}88", seed_id),
563 network: NetworkEnvironment::Main.to_std_string(),
564 wallet_only: Some(true),
565 authentication: Some(format!("username:{}", password)),
566 file_path: None,
567 ws_only: None,
568 ssh_host: None,
569 },
570 RpcUrl{
571 currency: SupportedCurrency::Monero,
572 url: "http://server:18089".to_string(),
573 network: NetworkEnvironment::Main.to_std_string(),
574 wallet_only: Some(false),
575 authentication: None,
576 file_path: None,
577 ws_only: None,
578 ssh_host: Some("server".to_string()),
579 },
580 ]
581
582}
583
584#[tokio::test]
586async fn local_three_node() {
587
588 if std::env::var("REDGOLD_DEBUG_DEVELOPER").is_err() {
589 return;
590 }
591
592 let ci = TestConstants::test_words_pass().unwrap();
593 let ci1 = ci.hash_derive_words("1").unwrap();
594 let ci2 = ci.hash_derive_words("2").unwrap();
595 let path = TestConstants::dev_ci_kp_path();
596 let pkh = ci.private_at(path.clone()).unwrap();
597 let pkh1 = ci1.private_at(path.clone()).unwrap();
598 let pkh2 = ci2.private_at(path.clone()).unwrap();
599 let net = NetworkEnvironment::Main;
600 let mut s = SSHProcessInvoke::new("server", None);
606 let user = std::env::var("USER").unwrap();
607 s.user = Some(user.clone());
608
609 let mut one = NodeConfig::from_test_id(&(1 as u16));
610 let mut two = NodeConfig::from_test_id(&(2 as u16));
611 let mut three = NodeConfig::from_test_id(&(3 as u16));
612 let mut four = NodeConfig::from_test_id(&(4 as u16));
613
614 one.set_words(ci.words.clone());
615 two.set_words(ci1.words.clone());
616 three.set_words(ci2.words.clone());
617 four.set_words(ci.words.clone());
620 one.network = NetworkEnvironment::Main;
622 two.network = NetworkEnvironment::Main;
623 three.network = NetworkEnvironment::Main;
624 four.network = NetworkEnvironment::Main;
625
626 one.set_rpcs(rpcs(1));
627 two.set_rpcs(rpcs(2));
628 three.set_rpcs(rpcs(3));
629 four.set_rpcs(rpcs(4));
630
631
632 let delete = false;
633 let mut one_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
634 &one, s.clone(), "/disk/monerotw2","~/wallet.exp".to_string(), Some(delete),
635 ).unwrap().unwrap();
636
637 let mut two_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
638 &two, s.clone(), "/disk/monerotw3","~/wallet.exp".to_string(), Some(delete)).unwrap().unwrap();
639 let mut three_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
640 &three, s.clone(), "/disk/monerotw4","~/wallet.exp".to_string(), Some(delete)).unwrap().unwrap();
641 let mut four_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
642 &four, s.clone(), "/disk/monerow","~/wallet.exp".to_string(), Some(false)).unwrap().unwrap();
643
644 let pub_keys = vec![
645 one.public_key.clone(),
646 two.public_key.clone(),
647 three.public_key.clone()
648 ];
649 let fnm = "test_multisig".to_string();
656 println!("Starting test on fnm {}", fnm.clone());
659 let mut rpc_vecs = vec![one_rpc.clone(), two_rpc.clone(), three_rpc.clone()];
661
662
663 let mut peer_strs: Vec<String> = vec![];
664
665
666 let mut loop_vec = vec![];
667 loop {
668 let mut new_peer_strs = vec![];
669 let mut last_ret = MoneroWalletMultisigRpcState::Unknown;
670
671 for (idx, rpc) in rpc_vecs.iter_mut().enumerate() {
672 let this_peer_strs = peer_strs.iter().enumerate().filter(
673 |(i, _)| *i != idx
674 ).map(|(_, s)| s.clone()).collect::<Vec<String>>();
675 let ret = rpc.multisig_create_next(
676 Some(this_peer_strs.clone()),
677 Some(2),
678 &fnm,
679 true,
680 2
681 ).await.unwrap();
682
683 if idx == 0 {
684 loop_vec.push(ret.clone());
685 }
686 println!("DONE wallet for peer {:?}", ret);
687 ret.multisig_info_string().map(|ss| new_peer_strs.push(ss));
688 if let Some(a) = ret.multisig_address_str() {
689 if !a.is_empty() {
690 println!("Multisig address: {}", a);
691 }
692 }
693 last_ret = ret;
694 }
695 peer_strs = new_peer_strs.clone();
696 if let MoneroWalletMultisigRpcState::MultisigReadyToSend = last_ret {
697 break;
698 }
699 }
700
701 let mut rpc_addrs_0 = vec![];
703 let mut msig_strs = vec![];
731
732 for (i, h) in rpc_vecs.iter_mut().enumerate() {
733 let info = h.export_multisig_info().await.unwrap();
734 msig_strs.push(info);
735 }
736 for (i, h) in rpc_vecs.iter_mut().enumerate() {
737 let info = h.import_multisig_info(msig_strs.clone()).await.unwrap();
738 println!("Imported multisig info: {:?}", info);
739 }
740
741 for (i, h) in rpc_vecs.iter_mut().enumerate() {
742 println!("Multisig address: {:?}", h.any_multisig_addr_history());
743 println!("Multisig balance {:?}", h.wallet_rpc.get_balance().await.unwrap().to_fractional());
744 let a = h.wallet_rpc.client.clone().wallet().get_address(0, None).await
745 .unwrap().address.to_string();
746 rpc_addrs_0.push(a.clone());
747 println!("RPC Address: {}", a)
748
749 }
755
756
757 println!("Done");
777 four_rpc.wallet_rpc.register_self_activate_ok(Some("hot".to_string()), None).await.unwrap();
778 let sync_info = four_rpc.wallet_rpc.refresh_sync_check_wallet().await.expect("refresh");
780 println!("sync info done: {:?}", sync_info);
781 let b = four_rpc.wallet_rpc.get_balance().await.unwrap();
783 println!("Balance: {:?}", b);
784 println!("Balance: {:?}", b.to_fractional());
785 println!("Address {}", four_rpc.wallet_rpc.self_address_str().unwrap());
786
787 let dest = four_rpc.wallet_rpc.self_address().unwrap();
792 let amt = CurrencyAmount::from_fractional_cur(0.004f64, SupportedCurrency::Monero).unwrap();
793 let send = vec![(dest, amt)];
794 let mut one = rpc_vecs.get(0).cloned().unwrap();
795 let (prep, tx) = one.multisig_send_prepare_and_sign(send).await.unwrap();
796 println!("Prepared: {:?}", prep);
797 println!("Tx: {:?}", tx);
798 let two = rpc_vecs.get(1).cloned().unwrap();
799 let res = two.wallet_rpc.clone().get_multisig().unwrap()
800 .sign_multisig(prep.multisig_txset.clone()).await.unwrap();
801 println!("Sign: {:?}", res);
802 }
831
832