lwk/
pset_details.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::{types::AssetId, Address, Txid};
4
5/// The details of a Partially Signed Elements Transaction:
6///
7/// - the net balance from the point of view of the wallet
8/// - the available and missing signatures for each input
9/// - for issuances and reissuances transactions contains the issuance or reissuance details
10#[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    /// Return the balance of the PSET from the point of view of the wallet
24    /// that generated this via `psetDetails()`
25    pub fn balance(&self) -> Arc<PsetBalance> {
26        Arc::new(self.inner.balance.clone().into())
27    }
28
29    /// For each input its existing or missing signatures
30    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    /// Return an element for every input that could possibly be a issuance or a reissuance
40    pub fn inputs_issuances(&self) -> Vec<Arc<Issuance>> {
41        // this is not aligned with what we are doing in app, where we offer a vec of only issuance and another with only reissuance
42        // with a reference to the relative input. We should problaby move that logic upper so we can reuse?
43        // in the meantime, this less ergonomic method should suffice.
44        self.inner
45            .issuances
46            .clone()
47            .into_iter()
48            .map(|e| Arc::new(e.into()))
49            .collect()
50    }
51
52    /// Set of fingerprints for which the PSET has a signature
53    pub fn fingerprints_has(&self) -> Vec<String> {
54        // TODO: return HashSet when upgrading to uniffi-rs 0.29 or later
55        self.inner
56            .fingerprints_has()
57            .into_iter()
58            .map(|fp| fp.to_string())
59            .collect()
60    }
61
62    /// Set of fingerprints for which the PSET is missing a signature
63    pub fn fingerprints_missing(&self) -> Vec<String> {
64        // TODO: return HashSet when upgrading to uniffi-rs 0.29 or later
65        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/// The details of an issuance or reissuance
151#[derive(uniffi::Object, Debug)]
152
153pub struct Issuance {
154    inner: lwk_common::Issuance,
155}
156
157#[uniffi::export]
158impl Issuance {
159    /// Return the asset id or None if it's a null issuance
160    pub fn asset(&self) -> Option<AssetId> {
161        self.inner.asset().map(Into::into)
162    }
163
164    /// Return the token id or None if it's a null issuance
165    pub fn token(&self) -> Option<AssetId> {
166        self.inner.token().map(Into::into)
167    }
168
169    /// Return the previous output index or None if it's a null issuance
170    pub fn prev_vout(&self) -> Option<u32> {
171        self.inner.prev_vout()
172    }
173
174    /// Return the previous transaction id or None if it's a null issuance
175    pub fn prev_txid(&self) -> Option<Arc<Txid>> {
176        self.inner.prev_txid().map(|e| Arc::new(e.into()))
177    }
178
179    /// Return true if the issuance or reissuance is null
180    pub fn is_null(&self) -> bool {
181        self.inner.is_null()
182    }
183
184    /// Return true if this is effectively an issuance
185    pub fn is_issuance(&self) -> bool {
186        self.inner.is_issuance()
187    }
188
189    /// Return true if this is effectively a reissuance
190    pub fn is_reissuance(&self) -> bool {
191        self.inner.is_reissuance()
192    }
193
194    /// Return true if the issuance or reissuance is confidential
195    pub fn is_confidential(&self) -> bool {
196        self.inner.is_confidential()
197    }
198
199    /// Return the amount of the asset in satoshis
200    pub fn asset_satoshi(&self) -> Option<u64> {
201        self.inner.asset_satoshi()
202    }
203
204    /// Return the amount of the reissuance token in satoshis
205    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}