1use std::{collections::HashMap, sync::Arc};
2
3use crate::{types::AssetId, Address, Txid};
4
5#[derive(uniffi::Object, Debug)]
11pub struct PsetDetails {
12 inner: lwk_common::PsetDetails,
13}
14
15impl From<lwk_common::PsetDetails> for PsetDetails {
16 fn from(inner: lwk_common::PsetDetails) -> Self {
17 Self { inner }
18 }
19}
20
21#[uniffi::export]
22impl PsetDetails {
23 pub fn balance(&self) -> Arc<PsetBalance> {
26 Arc::new(self.inner.balance.clone().into())
27 }
28
29 pub fn signatures(&self) -> Vec<Arc<PsetSignatures>> {
31 self.inner
32 .sig_details
33 .clone()
34 .into_iter()
35 .map(|s| Arc::new(s.into()))
36 .collect()
37 }
38
39 pub fn inputs_issuances(&self) -> Vec<Arc<Issuance>> {
41 self.inner
45 .issuances
46 .clone()
47 .into_iter()
48 .map(|e| Arc::new(e.into()))
49 .collect()
50 }
51
52 pub fn fingerprints_has(&self) -> Vec<String> {
54 self.inner
56 .fingerprints_has()
57 .into_iter()
58 .map(|fp| fp.to_string())
59 .collect()
60 }
61
62 pub fn fingerprints_missing(&self) -> Vec<String> {
64 self.inner
66 .fingerprints_missing()
67 .into_iter()
68 .map(|fp| fp.to_string())
69 .collect()
70 }
71}
72
73#[derive(uniffi::Object, Debug)]
74pub struct PsetBalance {
75 inner: lwk_common::PsetBalance,
76}
77
78impl From<lwk_common::PsetBalance> for PsetBalance {
79 fn from(inner: lwk_common::PsetBalance) -> Self {
80 Self { inner }
81 }
82}
83
84#[uniffi::export]
85impl PsetBalance {
86 pub fn fee(&self) -> u64 {
87 self.inner.fee
88 }
89
90 pub fn balances(&self) -> HashMap<AssetId, i64> {
91 self.inner
92 .balances
93 .iter()
94 .map(|(k, v)| ((*k).into(), *v))
95 .collect()
96 }
97
98 pub fn recipients(&self) -> Vec<Arc<Recipient>> {
99 self.inner
100 .recipients
101 .clone()
102 .into_iter()
103 .map(|e| Arc::new(e.into()))
104 .collect()
105 }
106}
107
108#[derive(uniffi::Object, Debug)]
109pub struct PsetSignatures {
110 inner: lwk_common::PsetSignatures,
111}
112
113impl From<lwk_common::PsetSignatures> for PsetSignatures {
114 fn from(inner: lwk_common::PsetSignatures) -> Self {
115 Self { inner }
116 }
117}
118
119type PublicKey = String;
120type KeySource = String;
121
122#[uniffi::export]
123impl PsetSignatures {
124 pub fn has_signature(&self) -> HashMap<PublicKey, KeySource> {
125 self.inner
126 .has_signature
127 .iter()
128 .map(|(k, v)| (k.to_string(), key_source_to_string(v)))
129 .collect()
130 }
131
132 pub fn missing_signature(&self) -> HashMap<PublicKey, KeySource> {
133 self.inner
134 .missing_signature
135 .iter()
136 .map(|(k, v)| (k.to_string(), key_source_to_string(v)))
137 .collect()
138 }
139}
140
141fn key_source_to_string(
142 key_source: &(
143 elements::bitcoin::bip32::Fingerprint,
144 elements::bitcoin::bip32::DerivationPath,
145 ),
146) -> String {
147 format!("[{}]{}", key_source.0, key_source.1)
148}
149
150#[derive(uniffi::Object, Debug)]
152
153pub struct Issuance {
154 inner: lwk_common::Issuance,
155}
156
157#[uniffi::export]
158impl Issuance {
159 pub fn asset(&self) -> Option<AssetId> {
161 self.inner.asset().map(Into::into)
162 }
163
164 pub fn token(&self) -> Option<AssetId> {
166 self.inner.token().map(Into::into)
167 }
168
169 pub fn prev_vout(&self) -> Option<u32> {
171 self.inner.prev_vout()
172 }
173
174 pub fn prev_txid(&self) -> Option<Arc<Txid>> {
176 self.inner.prev_txid().map(|e| Arc::new(e.into()))
177 }
178
179 pub fn is_null(&self) -> bool {
181 self.inner.is_null()
182 }
183
184 pub fn is_issuance(&self) -> bool {
186 self.inner.is_issuance()
187 }
188
189 pub fn is_reissuance(&self) -> bool {
191 self.inner.is_reissuance()
192 }
193
194 pub fn is_confidential(&self) -> bool {
196 self.inner.is_confidential()
197 }
198
199 pub fn asset_satoshi(&self) -> Option<u64> {
201 self.inner.asset_satoshi()
202 }
203
204 pub fn token_satoshi(&self) -> Option<u64> {
206 self.inner.token_satoshi()
207 }
208}
209
210impl From<lwk_common::Issuance> for Issuance {
211 fn from(inner: lwk_common::Issuance) -> Self {
212 Self { inner }
213 }
214}
215
216#[derive(uniffi::Object, Debug)]
217pub struct Recipient {
218 inner: lwk_common::Recipient,
219}
220
221impl From<lwk_common::Recipient> for Recipient {
222 fn from(inner: lwk_common::Recipient) -> Self {
223 Self { inner }
224 }
225}
226
227#[uniffi::export]
228impl Recipient {
229 pub fn asset(&self) -> Option<AssetId> {
230 self.inner.asset.map(Into::into)
231 }
232
233 pub fn value(&self) -> Option<u64> {
234 self.inner.value
235 }
236
237 pub fn address(&self) -> Option<Arc<Address>> {
238 self.inner
239 .address
240 .as_ref()
241 .map(|e| Arc::new(e.clone().into()))
242 }
243 pub fn vout(&self) -> u32 {
244 self.inner.vout
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use std::str::FromStr;
251
252 use crate::{types::AssetId, Network, Pset, Wollet, WolletDescriptor};
253
254 #[test]
255 fn pset_details() {
256 let pset = include_str!("../test_data/pset_details/pset.base64");
257 pset_details_(pset);
258 }
259
260 #[test]
261 fn pset_details_with_input_blind_proofs() {
262 let pset = include_str!("../test_data/pset_details/pset_with_input_blind_proofs.base64");
263 pset_details_(pset);
264 }
265
266 fn pset_details_(pset: &str) {
267 let pset = Pset::new(pset).unwrap();
268
269 let descriptor = include_str!("../test_data/pset_details/desc");
270 let descriptor = WolletDescriptor::new(descriptor).unwrap();
271 let network = Network::regtest_default();
272 let wollet = Wollet::new(&network, &descriptor, None).unwrap();
273
274 let details = wollet.pset_details(&pset).unwrap();
275 assert_eq!(details.balance().fee(), 254);
276
277 let balances = details.balance().balances();
278 assert_eq!(balances.len(), 1);
279 let expected_asset_id = "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225";
280 let asset_id = elements::AssetId::from_str(expected_asset_id).unwrap();
281 let asset_id: AssetId = asset_id.into();
282 let val = balances.get(&asset_id).unwrap();
283 assert_eq!(*val, -1254);
284
285 let signatures = details.signatures();
286 assert_eq!(signatures.len(), 1);
287
288 assert_eq!(format!("{:?}", signatures[0].has_signature()), "{\"02ab89406d9cf32ff1819838136eecb65c07add8e8ef1cd2d6c64bab1d85606453\": \"[6e055509]87'/1'/0'/0/0\"}");
289 assert_eq!(format!("{:?}", signatures[0].missing_signature()), "{\"03c1d0c7ddab5bd5bffbe0bf04a8a570eeabd9b6356358ecaacc242f658c7d5aad\": \"[281e2239]87'/1'/0'/0/0\"}");
290
291 let issuances = details.inputs_issuances();
292 assert_eq!(issuances.len(), 1);
293 assert!(!issuances[0].is_issuance());
294 assert!(!issuances[0].is_reissuance());
295 assert!(issuances[0].is_null());
296 assert!(!issuances[0].is_confidential());
297
298 let recipients = details.balance().recipients();
299 assert_eq!(recipients.len(), 1);
300 assert_eq!(recipients[0].vout(), 0);
301 assert_eq!(
302 recipients[0].asset().unwrap().to_string(),
303 "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
304 );
305 assert_eq!(recipients[0].value(), Some(1000));
306 assert_eq!(
307 recipients[0].address().unwrap().to_string(),
308 "AzpoyU5wJFcfdq6sh5ETbqCBA1oLuoLYk5UGJbYLGj3wKMurrVQiX1Djq67JHFAVt1hA5QVq41iNuVmy"
309 );
310
311 assert_eq!(details.fingerprints_has(), vec!["6e055509"]);
312 assert_eq!(details.fingerprints_missing(), vec!["281e2239"]);
313 }
314}