diff --git a/client/src/bin/space-cli.rs b/client/src/bin/space-cli.rs index 5322df5..66f2ccd 100644 --- a/client/src/bin/space-cli.rs +++ b/client/src/bin/space-cli.rs @@ -2,7 +2,7 @@ extern crate core; use std::{ fs, io, - io::{Cursor, IsTerminal}, + io::{Cursor, IsTerminal, Write}, path::PathBuf, }; @@ -78,6 +78,9 @@ enum Commands { /// Generate a new wallet #[command(name = "createwallet")] CreateWallet, + /// Restore wallet from mnemonic phrase + #[command(name = "restorewallet")] + RestoreWallet, /// Load a wallet #[command(name = "loadwallet")] LoadWallet, @@ -582,7 +585,17 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client print_list_wallets(result, cli.format); } Commands::CreateWallet => { - cli.client.wallet_create(&cli.wallet).await?; + let response = cli.client.wallet_create(&cli.wallet).await?; + println!("⚠️ Write down your recovery phrase NOW!"); + println!("This is the ONLY time it will be shown:"); + println!("{}", &response); + } + Commands::RestoreWallet => { + print!("Enter mnemonic phrase: "); + io::stdout().flush().unwrap(); + let mut mnemonic = String::new(); + io::stdin().read_line(&mut mnemonic).unwrap(); + cli.client.wallet_restore(&cli.wallet, mnemonic).await?; } Commands::LoadWallet => { cli.client.wallet_load(&cli.wallet).await?; diff --git a/client/src/rpc.rs b/client/src/rpc.rs index f91611b..f6c6910 100644 --- a/client/src/rpc.rs +++ b/client/src/rpc.rs @@ -230,7 +230,10 @@ pub trait Rpc { async fn wallet_export(&self, name: &str) -> Result; #[method(name = "walletcreate")] - async fn wallet_create(&self, name: &str) -> Result<(), ErrorObjectOwned>; + async fn wallet_create(&self, name: &str) -> Result; + + #[method(name = "walletrestore")] + async fn wallet_restore(&self, name: &str, mnemonic: String) -> Result<(), ErrorObjectOwned>; #[method(name = "walletsendrequest")] async fn wallet_send_request( @@ -491,11 +494,18 @@ impl WalletManager { Ok(export) } - pub async fn create_wallet(&self, client: &reqwest::Client, name: &str) -> anyhow::Result<()> { + pub async fn create_wallet(&self, client: &reqwest::Client, name: &str) -> anyhow::Result { let mnemonic: GeneratedKey<_, Tap> = Mnemonic::generate((WordCount::Words12, Language::English)) .map_err(|_| anyhow!("Mnemonic generation error"))?; + let start_block = self.get_wallet_start_block(client).await?; + self.setup_new_wallet(name.to_string(), mnemonic.to_string(), start_block)?; + self.load_wallet(name).await?; + Ok(mnemonic.to_string()) + } + + pub async fn restore_wallet(&self, client: &reqwest::Client, name: &str, mnemonic: &str) -> anyhow::Result<()> { let start_block = self.get_wallet_start_block(client).await?; self.setup_new_wallet(name.to_string(), mnemonic.to_string(), start_block)?; self.load_wallet(name).await?; @@ -513,7 +523,7 @@ impl WalletManager { return Err(anyhow!(format!("Wallet `{}` already exists", name))); } - let export = self.wallet_from_mnemonic(name.clone(), mnemonic.to_string(), start_block)?; + let export = self.wallet_from_mnemonic(name.clone(), mnemonic, start_block)?; fs::create_dir_all(&wallet_path)?; let wallet_export_path = wallet_path.join("wallet.json"); let mut file = fs::File::create(wallet_export_path)?; @@ -528,7 +538,7 @@ impl WalletManager { start_block: BlockId, ) -> anyhow::Result { let (network, _) = self.fallback_network(); - let xpriv = Self::descriptor_from_mnemonic(network, &mnemonic.to_string())?; + let xpriv = Self::descriptor_from_mnemonic(network, &mnemonic)?; let (external, internal) = Self::default_descriptors(xpriv); let tmp = bdk::Wallet::create(external, internal) @@ -653,7 +663,7 @@ impl WalletManager { } fn descriptor_from_mnemonic(network: Network, m: &str) -> anyhow::Result { - let mnemonic = Mnemonic::parse(m).unwrap(); + let mnemonic = Mnemonic::parse(m)?; let xkey: ExtendedKey = mnemonic.clone().into_extended_key()?; Ok(xkey.into_xprv(network).expect("xpriv")) } @@ -908,7 +918,7 @@ impl RpcServer for RpcServerImpl { }) } - async fn wallet_create(&self, name: &str) -> Result<(), ErrorObjectOwned> { + async fn wallet_create(&self, name: &str) -> Result { self.wallet_manager .create_wallet(&self.client, name) .await @@ -917,6 +927,15 @@ impl RpcServer for RpcServerImpl { }) } + async fn wallet_restore(&self, name: &str, mnemonic: String) -> Result<(), ErrorObjectOwned> { + self.wallet_manager + .restore_wallet(&self.client, name, &mnemonic) + .await + .map_err(|error| { + ErrorObjectOwned::owned(RPC_WALLET_NOT_LOADED, error.to_string(), None::) + }) + } + async fn wallet_send_request( &self, wallet: &str,