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
53#[derive(uniffi::Object, Debug)]
54pub struct PsetBalance {
55    inner: lwk_common::PsetBalance,
56}
57
58impl From<lwk_common::PsetBalance> for PsetBalance {
59    fn from(inner: lwk_common::PsetBalance) -> Self {
60        Self { inner }
61    }
62}
63
64#[uniffi::export]
65impl PsetBalance {
66    pub fn fee(&self) -> u64 {
67        self.inner.fee
68    }
69
70    pub fn balances(&self) -> HashMap<AssetId, i64> {
71        self.inner
72            .balances
73            .iter()
74            .map(|(k, v)| ((*k).into(), *v))
75            .collect()
76    }
77
78    pub fn recipients(&self) -> Vec<Arc<Recipient>> {
79        self.inner
80            .recipients
81            .clone()
82            .into_iter()
83            .map(|e| Arc::new(e.into()))
84            .collect()
85    }
86}
87
88#[derive(uniffi::Object, Debug)]
89pub struct PsetSignatures {
90    inner: lwk_common::PsetSignatures,
91}
92
93impl From<lwk_common::PsetSignatures> for PsetSignatures {
94    fn from(inner: lwk_common::PsetSignatures) -> Self {
95        Self { inner }
96    }
97}
98
99type PublicKey = String;
100type KeySource = String;
101
102#[uniffi::export]
103impl PsetSignatures {
104    pub fn has_signature(&self) -> HashMap<PublicKey, KeySource> {
105        self.inner
106            .has_signature
107            .iter()
108            .map(|(k, v)| (k.to_string(), key_source_to_string(v)))
109            .collect()
110    }
111
112    pub fn missing_signature(&self) -> HashMap<PublicKey, KeySource> {
113        self.inner
114            .missing_signature
115            .iter()
116            .map(|(k, v)| (k.to_string(), key_source_to_string(v)))
117            .collect()
118    }
119}
120
121fn key_source_to_string(
122    key_source: &(
123        elements::bitcoin::bip32::Fingerprint,
124        elements::bitcoin::bip32::DerivationPath,
125    ),
126) -> String {
127    format!("[{}]{}", key_source.0, key_source.1)
128}
129
130/// The details of an issuance or reissuance
131#[derive(uniffi::Object, Debug)]
132
133pub struct Issuance {
134    inner: lwk_common::Issuance,
135}
136
137#[uniffi::export]
138impl Issuance {
139    /// Return the asset id or None if it's a null issuance
140    pub fn asset(&self) -> Option<AssetId> {
141        self.inner.asset().map(Into::into)
142    }
143
144    /// Return the token id or None if it's a null issuance
145    pub fn token(&self) -> Option<AssetId> {
146        self.inner.token().map(Into::into)
147    }
148
149    /// Return the previous output index or None if it's a null issuance
150    pub fn prev_vout(&self) -> Option<u32> {
151        self.inner.prev_vout()
152    }
153
154    /// Return the previous transaction id or None if it's a null issuance
155    pub fn prev_txid(&self) -> Option<Arc<Txid>> {
156        self.inner.prev_txid().map(|e| Arc::new(e.into()))
157    }
158
159    /// Return true if the issuance or reissuance is null
160    pub fn is_null(&self) -> bool {
161        self.inner.is_null()
162    }
163
164    /// Return true if this is effectively an issuance
165    pub fn is_issuance(&self) -> bool {
166        self.inner.is_issuance()
167    }
168
169    /// Return true if this is effectively a reissuance
170    pub fn is_reissuance(&self) -> bool {
171        self.inner.is_reissuance()
172    }
173
174    /// Return true if the issuance or reissuance is confidential
175    pub fn is_confidential(&self) -> bool {
176        self.inner.is_confidential()
177    }
178
179    /// Return the amount of the asset in satoshis
180    pub fn asset_satoshi(&self) -> Option<u64> {
181        self.inner.asset_satoshi()
182    }
183
184    /// Return the amount of the reissuance token in satoshis
185    pub fn token_satoshi(&self) -> Option<u64> {
186        self.inner.token_satoshi()
187    }
188}
189
190impl From<lwk_common::Issuance> for Issuance {
191    fn from(inner: lwk_common::Issuance) -> Self {
192        Self { inner }
193    }
194}
195
196#[derive(uniffi::Object, Debug)]
197pub struct Recipient {
198    inner: lwk_common::Recipient,
199}
200
201impl From<lwk_common::Recipient> for Recipient {
202    fn from(inner: lwk_common::Recipient) -> Self {
203        Self { inner }
204    }
205}
206
207#[uniffi::export]
208impl Recipient {
209    pub fn asset(&self) -> Option<AssetId> {
210        self.inner.asset.map(Into::into)
211    }
212
213    pub fn value(&self) -> Option<u64> {
214        self.inner.value
215    }
216
217    pub fn address(&self) -> Option<Arc<Address>> {
218        self.inner
219            .address
220            .as_ref()
221            .map(|e| Arc::new(e.clone().into()))
222    }
223    pub fn vout(&self) -> u32 {
224        self.inner.vout
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use std::str::FromStr;
231
232    use crate::{types::AssetId, Network, Pset, Wollet, WolletDescriptor};
233
234    #[test]
235    fn pset_details() {
236        let pset = include_str!("../test_data/pset_details/pset.base64");
237        let pset = Pset::new(pset).unwrap();
238
239        let descriptor = include_str!("../test_data/pset_details/desc");
240        let descriptor = WolletDescriptor::new(descriptor).unwrap();
241        let network = Network::regtest_default();
242        let wollet = Wollet::new(&network, &descriptor, None).unwrap();
243
244        let details = wollet.pset_details(&pset).unwrap();
245        assert_eq!(details.balance().fee(), 254);
246
247        let balances = details.balance().balances();
248        assert_eq!(balances.len(), 1);
249        let expected_asset_id = "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225";
250        let asset_id = elements::AssetId::from_str(expected_asset_id).unwrap();
251        let asset_id: AssetId = asset_id.into();
252        let val = balances.get(&asset_id).unwrap();
253        assert_eq!(*val, -1254);
254
255        let signatures = details.signatures();
256        assert_eq!(signatures.len(), 1);
257
258        assert_eq!(format!("{:?}", signatures[0].has_signature()), "{\"02ab89406d9cf32ff1819838136eecb65c07add8e8ef1cd2d6c64bab1d85606453\": \"[6e055509]87'/1'/0'/0/0\"}");
259        assert_eq!(format!("{:?}", signatures[0].missing_signature()), "{\"03c1d0c7ddab5bd5bffbe0bf04a8a570eeabd9b6356358ecaacc242f658c7d5aad\": \"[281e2239]87'/1'/0'/0/0\"}");
260
261        let issuances = details.inputs_issuances();
262        assert_eq!(issuances.len(), 1);
263        assert!(!issuances[0].is_issuance());
264        assert!(!issuances[0].is_reissuance());
265        assert!(issuances[0].is_null());
266        assert!(!issuances[0].is_confidential());
267
268        let recipients = details.balance().recipients();
269        assert_eq!(recipients.len(), 1);
270        assert_eq!(recipients[0].vout(), 0);
271        assert_eq!(
272            recipients[0].asset().unwrap().to_string(),
273            "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
274        );
275        assert_eq!(recipients[0].value(), Some(1000));
276        assert_eq!(
277            recipients[0].address().unwrap().to_string(),
278            "AzpoyU5wJFcfdq6sh5ETbqCBA1oLuoLYk5UGJbYLGj3wKMurrVQiX1Djq67JHFAVt1hA5QVq41iNuVmy"
279        );
280    }
281}