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 let fee_paid = proofs_amount
45 .checked_sub(amount + change_amount)
46 .ok_or(Error::AmountOverflow)
47 .unwrap();
48
49 Ok(Self {
50 state,
51 preimage,
52 change: change_proofs,
53 amount,
54 fee_paid,
55 })
56 }
57
58 pub fn total_amount(&self) -> Amount {
60 self.amount + self.fee_paid
61 }
62}
63
64#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
66pub struct ProofInfo {
67 pub proof: Proof,
69 pub y: PublicKey,
71 pub mint_url: MintUrl,
73 pub state: State,
75 pub spending_condition: Option<SpendingConditions>,
77 pub unit: CurrencyUnit,
79}
80
81impl ProofInfo {
82 pub fn new(
84 proof: Proof,
85 mint_url: MintUrl,
86 state: State,
87 unit: CurrencyUnit,
88 ) -> Result<Self, Error> {
89 let y = proof.y()?;
90
91 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
92
93 Ok(Self {
94 proof,
95 y,
96 mint_url,
97 state,
98 spending_condition,
99 unit,
100 })
101 }
102
103 pub fn matches_conditions(
105 &self,
106 mint_url: &Option<MintUrl>,
107 unit: &Option<CurrencyUnit>,
108 state: &Option<Vec<State>>,
109 spending_conditions: &Option<Vec<SpendingConditions>>,
110 ) -> bool {
111 if let Some(mint_url) = mint_url {
112 if mint_url.ne(&self.mint_url) {
113 return false;
114 }
115 }
116
117 if let Some(unit) = unit {
118 if unit.ne(&self.unit) {
119 return false;
120 }
121 }
122
123 if let Some(state) = state {
124 if !state.contains(&self.state) {
125 return false;
126 }
127 }
128
129 if let Some(spending_conditions) = spending_conditions {
130 match &self.spending_condition {
131 None => {
132 if !spending_conditions.is_empty() {
133 return false;
134 }
135 }
136 Some(s) => {
137 if !spending_conditions.contains(s) {
138 return false;
139 }
140 }
141 }
142 }
143
144 true
145 }
146}
147
148#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
151pub struct PaymentProcessorKey {
152 pub unit: CurrencyUnit,
154 pub method: PaymentMethod,
156}
157
158impl PaymentProcessorKey {
159 pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
161 Self { unit, method }
162 }
163}
164
165#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
167pub struct QuoteTTL {
168 pub mint_ttl: u64,
170 pub melt_ttl: u64,
172}
173
174impl QuoteTTL {
175 pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
177 Self { mint_ttl, melt_ttl }
178 }
179}
180
181impl Default for QuoteTTL {
182 fn default() -> Self {
183 Self {
184 mint_ttl: 60 * 60, melt_ttl: 60, }
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use std::str::FromStr;
193
194 use cashu::SecretKey;
195
196 use super::{Melted, ProofInfo};
197 use crate::mint_url::MintUrl;
198 use crate::nuts::{CurrencyUnit, Id, Proof, PublicKey, SpendingConditions, State};
199 use crate::secret::Secret;
200 use crate::Amount;
201
202 #[test]
203 fn test_melted() {
204 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
205 let proof = Proof::new(
206 Amount::from(64),
207 keyset_id,
208 Secret::generate(),
209 PublicKey::from_hex(
210 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
211 )
212 .unwrap(),
213 );
214 let melted = Melted::from_proofs(
215 super::MeltQuoteState::Paid,
216 Some("preimage".to_string()),
217 Amount::from(64),
218 vec![proof.clone()],
219 None,
220 )
221 .unwrap();
222 assert_eq!(melted.amount, Amount::from(64));
223 assert_eq!(melted.fee_paid, Amount::ZERO);
224 assert_eq!(melted.total_amount(), Amount::from(64));
225 }
226
227 #[test]
228 fn test_melted_with_change() {
229 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
230 let proof = Proof::new(
231 Amount::from(64),
232 keyset_id,
233 Secret::generate(),
234 PublicKey::from_hex(
235 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
236 )
237 .unwrap(),
238 );
239 let change_proof = Proof::new(
240 Amount::from(32),
241 keyset_id,
242 Secret::generate(),
243 PublicKey::from_hex(
244 "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
245 )
246 .unwrap(),
247 );
248 let melted = Melted::from_proofs(
249 super::MeltQuoteState::Paid,
250 Some("preimage".to_string()),
251 Amount::from(31),
252 vec![proof.clone()],
253 Some(vec![change_proof.clone()]),
254 )
255 .unwrap();
256 assert_eq!(melted.amount, Amount::from(31));
257 assert_eq!(melted.fee_paid, Amount::from(1));
258 assert_eq!(melted.total_amount(), Amount::from(32));
259 }
260
261 #[test]
262 fn test_matches_conditions() {
263 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
264 let proof = Proof::new(
265 Amount::from(64),
266 keyset_id,
267 Secret::new("test_secret"),
268 PublicKey::from_hex(
269 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
270 )
271 .unwrap(),
272 );
273
274 let mint_url = MintUrl::from_str("https://example.com").unwrap();
275 let proof_info =
276 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
277
278 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
280 assert!(!proof_info.matches_conditions(
281 &Some(MintUrl::from_str("https://different.com").unwrap()),
282 &None,
283 &None,
284 &None
285 ));
286
287 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
289 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
290
291 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
293 assert!(proof_info.matches_conditions(
294 &None,
295 &None,
296 &Some(vec![State::Unspent, State::Spent]),
297 &None
298 ));
299 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
300
301 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
303
304 assert!(proof_info.matches_conditions(
306 &Some(mint_url),
307 &Some(CurrencyUnit::Sat),
308 &Some(vec![State::Unspent]),
309 &None
310 ));
311 }
312
313 #[test]
314 fn test_matches_conditions_with_spending_conditions() {
315 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
320 let proof = Proof::new(
321 Amount::from(64),
322 keyset_id,
323 Secret::new("test_secret"),
324 PublicKey::from_hex(
325 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
326 )
327 .unwrap(),
328 );
329
330 let mint_url = MintUrl::from_str("https://example.com").unwrap();
331 let proof_info =
332 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
333
334 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
336
337 let dummy_condition = SpendingConditions::P2PKConditions {
339 data: SecretKey::generate().public_key(),
340 conditions: None,
341 };
342 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct FeeReserve {
349 pub min_fee_reserve: Amount,
351 pub percent_fee_reserve: f32,
353}