kaspa_cli_lib/modules/
export.rs

1use crate::imports::*;
2use kaspa_wallet_core::account::{multisig::MultiSig, Account, BIP32_ACCOUNT_KIND, MULTISIG_ACCOUNT_KIND};
3
4#[derive(Default, Handler)]
5#[help("Export transactions, a wallet or a private key")]
6pub struct Export;
7
8impl Export {
9    async fn main(self: Arc<Self>, ctx: &Arc<dyn Context>, argv: Vec<String>, _cmd: &str) -> Result<()> {
10        let ctx = ctx.clone().downcast_arc::<KaspaCli>()?;
11
12        if argv.is_empty() || argv.first() == Some(&"help".to_string()) {
13            tprintln!(ctx, "usage: export [mnemonic]");
14            return Ok(());
15        }
16
17        let what = argv.first().unwrap();
18        match what.as_str() {
19            "mnemonic" => {
20                let account = ctx.account().await?;
21                if account.account_kind() == MULTISIG_ACCOUNT_KIND {
22                    let account = account.downcast_arc::<MultiSig>()?;
23                    export_multisig_account(ctx, account).await
24                } else {
25                    export_single_key_account(ctx, account).await
26                }
27            }
28            _ => Err(format!("Invalid argument: {}", what).into()),
29        }
30    }
31}
32
33async fn export_multisig_account(ctx: Arc<KaspaCli>, account: Arc<MultiSig>) -> Result<()> {
34    match &account.prv_key_data_ids() {
35        None => Err(Error::WatchOnlyAccountNoKeyData),
36        Some(v) if v.is_empty() => Err(Error::WatchOnlyAccountNoKeyData),
37        Some(prv_key_data_ids) => {
38            let wallet_secret = Secret::new(ctx.term().ask(true, "Enter wallet password: ").await?.trim().as_bytes().to_vec());
39            if wallet_secret.as_ref().is_empty() {
40                return Err(Error::WalletSecretRequired);
41            }
42
43            tprintln!(ctx, "required signatures: {}", account.minimum_signatures());
44            tprintln!(ctx, "");
45
46            let prv_key_data_store = ctx.store().as_prv_key_data_store()?;
47            let mut generated_xpub_keys = Vec::with_capacity(prv_key_data_ids.len());
48
49            for (id, prv_key_data_id) in prv_key_data_ids.iter().enumerate() {
50                let prv_key_data = prv_key_data_store.load_key_data(&wallet_secret, prv_key_data_id).await?.unwrap();
51                let mnemonic = prv_key_data.as_mnemonic(None).unwrap().unwrap();
52
53                let xpub_key: kaspa_bip32::ExtendedPublicKey<kaspa_bip32::secp256k1::PublicKey> =
54                    prv_key_data.create_xpub(None, MULTISIG_ACCOUNT_KIND.into(), 0).await?; // todo it can be done concurrently
55
56                tprintln!(ctx, "");
57                tprintln!(ctx, "extended public key {}:", id + 1);
58                tprintln!(ctx, "");
59                tprintln!(ctx, "{}", ctx.wallet().network_format_xpub(&xpub_key));
60                tprintln!(ctx, "");
61
62                tprintln!(ctx, "mnemonic {}:", id + 1);
63                tprintln!(ctx, "");
64                tprintln!(ctx, "{}", mnemonic.phrase());
65                tprintln!(ctx, "");
66
67                generated_xpub_keys.push(xpub_key);
68            }
69            let test = account.xpub_keys();
70
71            if let Some(keys) = test {
72                let additional = keys.iter().filter(|item| !generated_xpub_keys.contains(item));
73                additional.enumerate().for_each(|(idx, xpub)| {
74                    if idx == 0 {
75                        tprintln!(ctx, "additional xpubs: ");
76                    }
77                    tprintln!(ctx, "{}", ctx.wallet().network_format_xpub(xpub));
78                });
79            }
80            Ok(())
81        }
82    }
83}
84
85async fn export_single_key_account(ctx: Arc<KaspaCli>, account: Arc<dyn Account>) -> Result<()> {
86    let prv_key_data_id = account.prv_key_data_id()?;
87
88    let wallet_secret = Secret::new(ctx.term().ask(true, "Enter wallet password: ").await?.trim().as_bytes().to_vec());
89    if wallet_secret.as_ref().is_empty() {
90        return Err(Error::WalletSecretRequired);
91    }
92
93    let prv_key_data = ctx.store().as_prv_key_data_store()?.load_key_data(&wallet_secret, prv_key_data_id).await?;
94    let Some(keydata) = prv_key_data else { return Err(Error::KeyDataNotFound) };
95    let payment_secret = if keydata.payload.is_encrypted() {
96        let payment_secret = Secret::new(ctx.term().ask(true, "Enter payment password: ").await?.trim().as_bytes().to_vec());
97        if payment_secret.as_ref().is_empty() {
98            return Err(Error::PaymentSecretRequired);
99        } else {
100            Some(payment_secret)
101        }
102    } else {
103        None
104    };
105
106    let prv_key_data = keydata.payload.decrypt(payment_secret.as_ref())?;
107    let mnemonic = prv_key_data.as_ref().as_mnemonic()?;
108
109    let xpub_key = keydata.create_xpub(None, BIP32_ACCOUNT_KIND.into(), 0).await?; // todo it can be done concurrently
110
111    tprintln!(ctx, "extended public key:");
112    tprintln!(ctx, "");
113    tprintln!(ctx, "{}", ctx.wallet().network_format_xpub(&xpub_key));
114    tprintln!(ctx, "");
115
116    match mnemonic {
117        None => {
118            tprintln!(ctx, "mnemonic is not available for this private key");
119        }
120        Some(mnemonic) if payment_secret.is_none() => {
121            tprintln!(ctx, "mnemonic:");
122            tprintln!(ctx, "");
123            tprintln!(ctx, "{}", mnemonic.phrase());
124            tprintln!(ctx, "");
125        }
126        Some(mnemonic) => {
127            tpara!(
128                ctx,
129                "\
130                                IMPORTANT: to recover your private key using this mnemonic in the future \
131                                you will need your payment password. Your payment password is permanently associated with \
132                                this mnemonic.",
133            );
134            tprintln!(ctx, "");
135            tprintln!(ctx, "mnemonic:");
136            tprintln!(ctx, "");
137            tprintln!(ctx, "{}", mnemonic.phrase());
138            tprintln!(ctx, "");
139        }
140    };
141
142    Ok(())
143}