1use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6use crate::mint_url::MintUrl;
7use crate::nuts::nut00::ProofsMethods;
8use crate::nuts::{
9 CurrencyUnit, MeltQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SpendingConditions,
10 State,
11};
12use crate::Amount;
13
14#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
16pub struct Melted {
17 pub state: MeltQuoteState,
19 pub preimage: Option<String>,
21 pub change: Option<Proofs>,
23 pub amount: Amount,
25 pub fee_paid: Amount,
27}
28
29impl Melted {
30 pub fn from_proofs(
32 state: MeltQuoteState,
33 preimage: Option<String>,
34 amount: Amount,
35 proofs: Proofs,
36 change_proofs: Option<Proofs>,
37 ) -> Result<Self, Error> {
38 let proofs_amount = proofs.total_amount()?;
39 let change_amount = match &change_proofs {
40 Some(change_proofs) => change_proofs.total_amount()?,
41 None => Amount::ZERO,
42 };
43
44 tracing::info!(
45 "Proofs amount: {} Amount: {} Change: {}",
46 proofs_amount,
47 amount,
48 change_amount
49 );
50
51 let fee_paid = proofs_amount
52 .checked_sub(amount + change_amount)
53 .ok_or(Error::AmountOverflow)?;
54
55 Ok(Self {
56 state,
57 preimage,
58 change: change_proofs,
59 amount,
60 fee_paid,
61 })
62 }
63
64 pub fn total_amount(&self) -> Amount {
66 self.amount + self.fee_paid
67 }
68}
69
70#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ProofInfo {
73 pub proof: Proof,
75 pub y: PublicKey,
77 pub mint_url: MintUrl,
79 pub state: State,
81 pub spending_condition: Option<SpendingConditions>,
83 pub unit: CurrencyUnit,
85}
86
87impl ProofInfo {
88 pub fn new(
90 proof: Proof,
91 mint_url: MintUrl,
92 state: State,
93 unit: CurrencyUnit,
94 ) -> Result<Self, Error> {
95 let y = proof.y()?;
96
97 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
98
99 Ok(Self {
100 proof,
101 y,
102 mint_url,
103 state,
104 spending_condition,
105 unit,
106 })
107 }
108
109 pub fn matches_conditions(
111 &self,
112 mint_url: &Option<MintUrl>,
113 unit: &Option<CurrencyUnit>,
114 state: &Option<Vec<State>>,
115 spending_conditions: &Option<Vec<SpendingConditions>>,
116 ) -> bool {
117 if let Some(mint_url) = mint_url {
118 if mint_url.ne(&self.mint_url) {
119 return false;
120 }
121 }
122
123 if let Some(unit) = unit {
124 if unit.ne(&self.unit) {
125 return false;
126 }
127 }
128
129 if let Some(state) = state {
130 if !state.contains(&self.state) {
131 return false;
132 }
133 }
134
135 if let Some(spending_conditions) = spending_conditions {
136 match &self.spending_condition {
137 None => {
138 if !spending_conditions.is_empty() {
139 return false;
140 }
141 }
142 Some(s) => {
143 if !spending_conditions.contains(s) {
144 return false;
145 }
146 }
147 }
148 }
149
150 true
151 }
152}
153
154#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
157pub struct PaymentProcessorKey {
158 pub unit: CurrencyUnit,
160 pub method: PaymentMethod,
162}
163
164impl PaymentProcessorKey {
165 pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
167 Self { unit, method }
168 }
169}
170
171#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
173pub struct QuoteTTL {
174 pub mint_ttl: u64,
176 pub melt_ttl: u64,
178}
179
180impl QuoteTTL {
181 pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
183 Self { mint_ttl, melt_ttl }
184 }
185}
186
187impl Default for QuoteTTL {
188 fn default() -> Self {
189 Self {
190 mint_ttl: 60 * 60, melt_ttl: 60, }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use std::str::FromStr;
199
200 use cashu::SecretKey;
201
202 use super::{Melted, ProofInfo};
203 use crate::mint_url::MintUrl;
204 use crate::nuts::{CurrencyUnit, Id, Proof, PublicKey, SpendingConditions, State};
205 use crate::secret::Secret;
206 use crate::Amount;
207
208 #[test]
209 fn test_melted() {
210 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
211 let proof = Proof::new(
212 Amount::from(64),
213 keyset_id,
214 Secret::generate(),
215 PublicKey::from_hex(
216 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
217 )
218 .unwrap(),
219 );
220 let melted = Melted::from_proofs(
221 super::MeltQuoteState::Paid,
222 Some("preimage".to_string()),
223 Amount::from(64),
224 vec![proof.clone()],
225 None,
226 )
227 .unwrap();
228 assert_eq!(melted.amount, Amount::from(64));
229 assert_eq!(melted.fee_paid, Amount::ZERO);
230 assert_eq!(melted.total_amount(), Amount::from(64));
231 }
232
233 #[test]
234 fn test_melted_with_change() {
235 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
236 let proof = Proof::new(
237 Amount::from(64),
238 keyset_id,
239 Secret::generate(),
240 PublicKey::from_hex(
241 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
242 )
243 .unwrap(),
244 );
245 let change_proof = Proof::new(
246 Amount::from(32),
247 keyset_id,
248 Secret::generate(),
249 PublicKey::from_hex(
250 "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
251 )
252 .unwrap(),
253 );
254 let melted = Melted::from_proofs(
255 super::MeltQuoteState::Paid,
256 Some("preimage".to_string()),
257 Amount::from(31),
258 vec![proof.clone()],
259 Some(vec![change_proof.clone()]),
260 )
261 .unwrap();
262 assert_eq!(melted.amount, Amount::from(31));
263 assert_eq!(melted.fee_paid, Amount::from(1));
264 assert_eq!(melted.total_amount(), Amount::from(32));
265 }
266
267 #[test]
268 fn test_matches_conditions() {
269 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
270 let proof = Proof::new(
271 Amount::from(64),
272 keyset_id,
273 Secret::new("test_secret"),
274 PublicKey::from_hex(
275 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
276 )
277 .unwrap(),
278 );
279
280 let mint_url = MintUrl::from_str("https://example.com").unwrap();
281 let proof_info =
282 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
283
284 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
286 assert!(!proof_info.matches_conditions(
287 &Some(MintUrl::from_str("https://different.com").unwrap()),
288 &None,
289 &None,
290 &None
291 ));
292
293 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
295 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
296
297 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
299 assert!(proof_info.matches_conditions(
300 &None,
301 &None,
302 &Some(vec![State::Unspent, State::Spent]),
303 &None
304 ));
305 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
306
307 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
309
310 assert!(proof_info.matches_conditions(
312 &Some(mint_url),
313 &Some(CurrencyUnit::Sat),
314 &Some(vec![State::Unspent]),
315 &None
316 ));
317 }
318
319 #[test]
320 fn test_matches_conditions_with_spending_conditions() {
321 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
326 let proof = Proof::new(
327 Amount::from(64),
328 keyset_id,
329 Secret::new("test_secret"),
330 PublicKey::from_hex(
331 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
332 )
333 .unwrap(),
334 );
335
336 let mint_url = MintUrl::from_str("https://example.com").unwrap();
337 let proof_info =
338 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
339
340 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
342
343 let dummy_condition = SpendingConditions::P2PKConditions {
345 data: SecretKey::generate().public_key(),
346 conditions: None,
347 };
348 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
349 }
350}
351
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
354pub struct FeeReserve {
355 pub min_fee_reserve: Amount,
357 pub percent_fee_reserve: f32,
359}