1use 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#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18pub struct WalletKey {
19 pub mint_url: MintUrl,
21 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 pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
34 Self { mint_url, unit }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct MintQuote {
41 pub id: String,
43 pub mint_url: MintUrl,
45 pub amount: Amount,
47 pub unit: CurrencyUnit,
49 pub request: String,
51 pub state: MintQuoteState,
53 pub expiry: u64,
55 pub secret_key: Option<SecretKey>,
57}
58
59#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
61pub struct MeltQuote {
62 pub id: String,
64 pub unit: CurrencyUnit,
66 pub amount: Amount,
68 pub request: String,
70 pub fee_reserve: Amount,
72 pub state: MeltQuoteState,
74 pub expiry: u64,
76 pub payment_preimage: Option<String>,
78}
79
80#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
82pub enum SendKind {
83 #[default]
84 OnlineExact,
86 OnlineTolerance(Amount),
88 OfflineExact,
90 OfflineTolerance(Amount),
92}
93
94impl SendKind {
95 pub fn is_online(&self) -> bool {
97 matches!(self, Self::OnlineExact | Self::OnlineTolerance(_))
98 }
99
100 pub fn is_offline(&self) -> bool {
102 matches!(self, Self::OfflineExact | Self::OfflineTolerance(_))
103 }
104
105 pub fn is_exact(&self) -> bool {
107 matches!(self, Self::OnlineExact | Self::OfflineExact)
108 }
109
110 pub fn has_tolerance(&self) -> bool {
112 matches!(self, Self::OnlineTolerance(_) | Self::OfflineTolerance(_))
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct Transaction {
119 pub mint_url: MintUrl,
121 pub direction: TransactionDirection,
123 pub amount: Amount,
125 pub fee: Amount,
127 pub unit: CurrencyUnit,
129 pub ys: Vec<PublicKey>,
131 pub timestamp: u64,
133 pub memo: Option<String>,
135 pub metadata: HashMap<String, String>,
137}
138
139impl Transaction {
140 pub fn id(&self) -> TransactionId {
142 TransactionId::new(self.ys.clone())
143 }
144
145 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#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub enum TransactionDirection {
186 Incoming,
188 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
215#[serde(transparent)]
216pub struct TransactionId([u8; 32]);
217
218impl TransactionId {
219 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 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 pub fn from_bytes(bytes: [u8; 32]) -> Self {
242 Self(bytes)
243 }
244
245 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 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 pub fn as_bytes(&self) -> &[u8; 32] {
265 &self.0
266 }
267
268 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}