bhwi_cli/
get_descriptors.rs1use crate::DeviceManager;
6use anyhow::Result;
7use async_trait::async_trait;
8
9pub use bhwi_async::descriptors::*;
10use miniscript::{Descriptor, DescriptorPublicKey};
11use serde::{Serialize, Serializer};
12
13use crate::{OutputFormat, select_device};
14
15#[derive(Debug, Clone, Serialize)]
17pub struct KeypoolDescriptor {
18 #[serde(serialize_with = "serialize_descriptor")]
20 pub descriptor: Descriptor<DescriptorPublicKey>,
21 pub range: [u32; 2],
23 pub internal: bool,
25 pub keypool: bool,
27}
28
29fn serialize_descriptor<S>(
30 descriptor: &Descriptor<DescriptorPublicKey>,
31 ser: S,
32) -> Result<S::Ok, S::Error>
33where
34 S: Serializer,
35{
36 ser.serialize_str(&format!("{descriptor:#}"))
37}
38
39#[async_trait(?Send)]
43pub trait DescriptorOutput {
44 async fn get_keypool(
50 &self,
51 options: GetKeypoolOptions,
52 format: Option<OutputFormat>,
53 ) -> Result<()>;
54 async fn get_pubkey_descriptors(
61 &self,
62 account: Option<u32>,
63 format: Option<OutputFormat>,
64 ) -> Result<()>;
65}
66
67#[async_trait(?Send)]
68impl DescriptorOutput for DeviceManager {
69 async fn get_keypool(
70 &self,
71 options: GetKeypoolOptions,
72 format: Option<OutputFormat>,
73 ) -> Result<()> {
74 let Some(mut device) = select_device(self).await? else {
75 return Ok(());
76 };
77 let master_fingerprint = device.fingerprint().await?;
78 let descriptor =
79 get_keypool_descriptor(device.device().as_mut(), master_fingerprint, &options).await?;
80 let keypool = KeypoolDescriptor {
81 descriptor,
82 range: [options.start, options.end],
83 internal: options.internal,
84 keypool: true,
85 };
86 match format {
87 Some(OutputFormat::Json) => println!("{}", serde_json::to_string(&keypool)?),
88 Some(OutputFormat::Pretty) => {
89 println!("{:<9} | {:<8} | {:<10}", "Range", "Internal", "Descriptor");
90 println!("{}", "-".repeat(120));
91 println!(
92 "{}-{} | {:<8} | {}",
93 keypool.range[0], keypool.range[1], keypool.internal, keypool.descriptor
94 );
95 }
96 None => {
97 println!(
98 "{:#} range={}-{} internal={} keypool=true",
99 keypool.descriptor, keypool.range[0], keypool.range[1], keypool.internal
100 );
101 }
102 }
103 Ok(())
104 }
105
106 async fn get_pubkey_descriptors(
109 &self,
110 account: Option<u32>,
111 format: Option<OutputFormat>,
112 ) -> Result<()> {
113 let Some(mut device) = select_device(self).await? else {
114 return Ok(());
115 };
116 let network = self.selector.network;
117 let fingerprint = device.fingerprint().await?;
118 let PubkeyDescriptors { receive, internal } = get_pubkey_descriptors(
119 device.device().as_mut(),
120 fingerprint,
121 account.unwrap_or(0),
122 network,
123 )
124 .await?;
125 match format {
126 Some(OutputFormat::Pretty) => {
127 let header = format!("{:<10} | {:<120}", "Purpose", "Descriptor");
128 println!("{}", header);
129 println!("{}", "-".repeat(header.len()));
130 for (purpose, items) in [("internal", internal), ("receive", receive)] {
131 for item in items {
132 println!("{:<10} | {:<120}", purpose, item);
133 }
134 println!("{}", "-".repeat(header.len()));
135 }
136 }
137 Some(OutputFormat::Json) => {
138 println!(
139 "{}",
140 serde_json::json!(
141 {
142 "receive": receive,
143 "internal": internal
144 })
145 );
146 }
147 None => {
148 receive
149 .iter()
150 .chain(internal.iter())
151 .for_each(|d| println!("{d:#}"));
152 }
153 }
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use std::str::FromStr;
161
162 use miniscript::{Descriptor, DescriptorPublicKey};
163
164 use super::KeypoolDescriptor;
165
166 #[test]
167 fn keypool_descriptor_serializes_json_shape() {
168 let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
169 "wpkh([e3ebcc79/84'/1'/0']tpubDCKD5cdxMEFd2i4cNa3PJUbUHMsGDxsnfqjxVpMoG1ymWYUQUaZzTcHQo3JwYgaKe2FyKGA2FzGPSVczBoAiHGyERuA1mZ2UkGKufEnUxKk/0/*)",
170 )
171 .unwrap();
172 let keypool = KeypoolDescriptor {
173 descriptor,
174 range: [7, 11],
175 internal: false,
176 keypool: true,
177 };
178
179 let json = serde_json::to_value(keypool).unwrap();
180
181 assert_eq!(json["range"], serde_json::json!([7, 11]));
182 assert_eq!(json["internal"], false);
183 assert_eq!(json["keypool"], true);
184 assert!(json["descriptor"].as_str().unwrap().starts_with("wpkh("));
185 }
186}