async_hwi_cli/
lib.rs

1pub mod command {
2    use async_hwi::{
3        bitbox::{api::runtime, BitBox02, PairingBitbox02WithLocalCache},
4        coldcard,
5        jade::{self, Jade},
6        ledger::{HidApi, Ledger, LedgerSimulator, TransportHID},
7        specter::{Specter, SpecterSimulator},
8        HWI,
9    };
10    use bitcoin::{hashes::hex::FromHex, Network};
11    use std::error::Error;
12
13    pub struct Wallet<'a> {
14        pub name: Option<&'a String>,
15        pub policy: Option<&'a String>,
16        pub hmac: Option<&'a String>,
17    }
18
19    pub async fn list(
20        network: Network,
21        wallet: Option<Wallet<'_>>,
22    ) -> Result<Vec<Box<dyn HWI + Send>>, Box<dyn Error>> {
23        let mut hws = Vec::new();
24
25        if let Ok(device) = SpecterSimulator::try_connect().await {
26            hws.push(device.into());
27        }
28
29        if let Ok(devices) = Specter::enumerate().await {
30            for device in devices {
31                hws.push(device.into());
32            }
33        }
34
35        match Jade::enumerate().await {
36            Err(e) => println!("{:?}", e),
37            Ok(devices) => {
38                for device in devices {
39                    let device = device.with_network(network);
40                    if let Ok(info) = device.get_info().await {
41                        if info.jade_state == jade::api::JadeState::Locked {
42                            if let Err(e) = device.auth().await {
43                                eprintln!("auth {:?}", e);
44                                continue;
45                            }
46                        }
47
48                        hws.push(device.into());
49                    }
50                }
51            }
52        }
53
54        if let Ok(device) = LedgerSimulator::try_connect().await {
55            hws.push(device.into());
56        }
57
58        let api = Box::new(HidApi::new().unwrap());
59
60        for device_info in api.device_list() {
61            if async_hwi::bitbox::is_bitbox02(device_info) {
62                if let Ok(device) = device_info.open_device(&api) {
63                    if let Ok(device) =
64                        PairingBitbox02WithLocalCache::<runtime::TokioRuntime>::connect(
65                            device, None,
66                        )
67                        .await
68                    {
69                        if let Ok((device, _)) = device.wait_confirm().await {
70                            let mut bb02 = BitBox02::from(device).with_network(network);
71                            if let Some(policy) = wallet.as_ref().and_then(|w| w.policy) {
72                                bb02 = bb02.with_policy(policy)?;
73                            }
74                            hws.push(bb02.into());
75                        }
76                    }
77                }
78            }
79            if device_info.vendor_id() == coldcard::api::COINKITE_VID
80                && device_info.product_id() == coldcard::api::CKCC_PID
81            {
82                if let Some(sn) = device_info.serial_number() {
83                    if let Ok((cc, _)) = coldcard::api::Coldcard::open(&api, sn, None) {
84                        let mut hw = coldcard::Coldcard::from(cc);
85                        if let Some(ref wallet) = wallet {
86                            hw = hw.with_wallet_name(
87                                wallet
88                                    .name
89                                    .ok_or::<Box<dyn Error>>(
90                                        "coldcard requires a wallet name".into(),
91                                    )?
92                                    .to_string(),
93                            );
94                        }
95                        hws.push(hw.into())
96                    }
97                }
98            }
99        }
100
101        for detected in Ledger::<TransportHID>::enumerate(&api) {
102            if let Ok(mut device) = Ledger::<TransportHID>::connect(&api, detected) {
103                if let Some(ref wallet) = wallet {
104                    let hmac = if let Some(s) = wallet.hmac {
105                        let mut h = [b'\0'; 32];
106                        h.copy_from_slice(&Vec::from_hex(s)?);
107                        Some(h)
108                    } else {
109                        None
110                    };
111                    device = device.with_wallet(
112                        wallet
113                            .name
114                            .ok_or::<Box<dyn Error>>("ledger requires a wallet name".into())?,
115                        wallet
116                            .policy
117                            .ok_or::<Box<dyn Error>>("ledger requires a wallet policy".into())?,
118                        hmac,
119                    )?;
120                }
121                hws.push(device.into());
122            }
123        }
124
125        Ok(hws)
126    }
127}