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}