cdk_common/
wallet.rs

1//! Wallet Types
2
3use std::collections::HashMap;
4use std::fmt;
5use std::str::FromStr;
6
7use bitcoin::hashes::{sha256, Hash, HashEngine};
8use cashu::util::hex;
9use cashu::{nut00, Proofs, PublicKey};
10use serde::{Deserialize, Serialize};
11
12use crate::mint_url::MintUrl;
13use crate::nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, SecretKey};
14use crate::{Amount, Error};
15
16/// Wallet Key
17#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18pub struct WalletKey {
19    /// Mint Url
20    pub mint_url: MintUrl,
21    /// Currency Unit
22    pub unit: CurrencyUnit,
23}
24
25impl fmt::Display for WalletKey {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
28    }
29}
30
31impl WalletKey {
32    /// Create new [`WalletKey`]
33    pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
34        Self { mint_url, unit }
35    }
36}
37
38/// Mint Quote Info
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct MintQuote {
41    /// Quote id
42    pub id: String,
43    /// Mint Url
44    pub mint_url: MintUrl,
45    /// Amount of quote
46    pub amount: Amount,
47    /// Unit of quote
48    pub unit: CurrencyUnit,
49    /// Quote payment request e.g. bolt11
50    pub request: String,
51    /// Quote state
52    pub state: MintQuoteState,
53    /// Expiration time of quote
54    pub expiry: u64,
55    /// Secretkey for signing mint quotes [NUT-20]
56    pub secret_key: Option<SecretKey>,
57}
58
59/// Melt Quote Info
60#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
61pub struct MeltQuote {
62    /// Quote id
63    pub id: String,
64    /// Quote unit
65    pub unit: CurrencyUnit,
66    /// Quote amount
67    pub amount: Amount,
68    /// Quote Payment request e.g. bolt11
69    pub request: String,
70    /// Quote fee reserve
71    pub fee_reserve: Amount,
72    /// Quote state
73    pub state: MeltQuoteState,
74    /// Expiration time of quote
75    pub expiry: u64,
76    /// Payment preimage
77    pub payment_preimage: Option<String>,
78}
79
80/// Send Kind
81#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
82pub enum SendKind {
83    #[default]
84    /// Allow online swap before send if wallet does not have exact amount
85    OnlineExact,
86    /// Prefer offline send if difference is less then tolerance
87    OnlineTolerance(Amount),
88    /// Wallet cannot do an online swap and selected proof must be exactly send amount
89    OfflineExact,
90    /// Wallet must remain offline but can over pay if below tolerance
91    OfflineTolerance(Amount),
92}
93
94impl SendKind {
95    /// Check if send kind is online
96    pub fn is_online(&self) -> bool {
97        matches!(self, Self::OnlineExact | Self::OnlineTolerance(_))
98    }
99
100    /// Check if send kind is offline
101    pub fn is_offline(&self) -> bool {
102        matches!(self, Self::OfflineExact | Self::OfflineTolerance(_))
103    }
104
105    /// Check if send kind is exact
106    pub fn is_exact(&self) -> bool {
107        matches!(self, Self::OnlineExact | Self::OfflineExact)
108    }
109
110    /// Check if send kind has tolerance
111    pub fn has_tolerance(&self) -> bool {
112        matches!(self, Self::OnlineTolerance(_) | Self::OfflineTolerance(_))
113    }
114}
115
116/// Wallet Transaction
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct Transaction {
119    /// Mint Url
120    pub mint_url: MintUrl,
121    /// Transaction direction
122    pub direction: TransactionDirection,
123    /// Amount
124    pub amount: Amount,
125    /// Fee
126    pub fee: Amount,
127    /// Currency Unit
128    pub unit: CurrencyUnit,
129    /// Proof Ys
130    pub ys: Vec<PublicKey>,
131    /// Unix timestamp
132    pub timestamp: u64,
133    /// Memo
134    pub memo: Option<String>,
135    /// User-defined metadata
136    pub metadata: HashMap<String, String>,
137}
138
139impl Transaction {
140    /// Transaction ID
141    pub fn id(&self) -> TransactionId {
142        TransactionId::new(self.ys.clone())
143    }
144
145    /// Check if transaction matches conditions
146    pub fn matches_conditions(
147        &self,
148        mint_url: &Option<MintUrl>,
149        direction: &Option<TransactionDirection>,
150        unit: &Option<CurrencyUnit>,
151    ) -> bool {
152        if let Some(mint_url) = mint_url {
153            if &self.mint_url != mint_url {
154                return false;
155            }
156        }
157        if let Some(direction) = direction {
158            if &self.direction != direction {
159                return false;
160            }
161        }
162        if let Some(unit) = unit {
163            if &self.unit != unit {
164                return false;
165            }
166        }
167        true
168    }
169}
170
171impl PartialOrd for Transaction {
172    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
173        Some(self.cmp(other))
174    }
175}
176
177impl Ord for Transaction {
178    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
179        self.timestamp.cmp(&other.timestamp).reverse()
180    }
181}
182
183/// Transaction Direction
184#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub enum TransactionDirection {
186    /// Incoming transaction (i.e., receive or mint)
187    Incoming,
188    /// Outgoing transaction (i.e., send or melt)
189    Outgoing,
190}
191
192impl std::fmt::Display for TransactionDirection {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        match self {
195            TransactionDirection::Incoming => write!(f, "Incoming"),
196            TransactionDirection::Outgoing => write!(f, "Outgoing"),
197        }
198    }
199}
200
201impl FromStr for TransactionDirection {
202    type Err = Error;
203
204    fn from_str(value: &str) -> Result<Self, Self::Err> {
205        match value {
206            "Incoming" => Ok(Self::Incoming),
207            "Outgoing" => Ok(Self::Outgoing),
208            _ => Err(Error::InvalidTransactionDirection),
209        }
210    }
211}
212
213/// Transaction ID
214#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
215#[serde(transparent)]
216pub struct TransactionId([u8; 32]);
217
218impl TransactionId {
219    /// Create new [`TransactionId`]
220    pub fn new(ys: Vec<PublicKey>) -> Self {
221        let mut ys = ys;
222        ys.sort();
223        let mut hasher = sha256::Hash::engine();
224        for y in ys {
225            hasher.input(&y.to_bytes());
226        }
227        let hash = sha256::Hash::from_engine(hasher);
228        Self(hash.to_byte_array())
229    }
230
231    /// From proofs
232    pub fn from_proofs(proofs: Proofs) -> Result<Self, nut00::Error> {
233        let ys = proofs
234            .iter()
235            .map(|proof| proof.y())
236            .collect::<Result<Vec<PublicKey>, nut00::Error>>()?;
237        Ok(Self::new(ys))
238    }
239
240    /// From bytes
241    pub fn from_bytes(bytes: [u8; 32]) -> Self {
242        Self(bytes)
243    }
244
245    /// From hex string
246    pub fn from_hex(value: &str) -> Result<Self, Error> {
247        let bytes = hex::decode(value)?;
248        let mut array = [0u8; 32];
249        array.copy_from_slice(&bytes);
250        Ok(Self(array))
251    }
252
253    /// From slice
254    pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
255        if slice.len() != 32 {
256            return Err(Error::InvalidTransactionId);
257        }
258        let mut array = [0u8; 32];
259        array.copy_from_slice(slice);
260        Ok(Self(array))
261    }
262
263    /// Get inner value
264    pub fn as_bytes(&self) -> &[u8; 32] {
265        &self.0
266    }
267
268    /// Get inner value as slice
269    pub fn as_slice(&self) -> &[u8] {
270        &self.0
271    }
272}
273
274impl std::fmt::Display for TransactionId {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(f, "{}", hex::encode(self.0))
277    }
278}
279
280impl FromStr for TransactionId {
281    type Err = Error;
282
283    fn from_str(value: &str) -> Result<Self, Self::Err> {
284        Self::from_hex(value)
285    }
286}
287
288impl TryFrom<Proofs> for TransactionId {
289    type Error = nut00::Error;
290
291    fn try_from(proofs: Proofs) -> Result<Self, Self::Error> {
292        Self::from_proofs(proofs)
293    }
294}